From 6373485692cb23be9b87a514b55b4600608d07e6 Mon Sep 17 00:00:00 2001 From: Christian Marillat Date: Sun, 17 Apr 2022 08:29:18 +0100 Subject: [PATCH] Import libtorrent-rasterbar_2.0.6.orig.tar.xz [dgit import orig libtorrent-rasterbar_2.0.6.orig.tar.xz] --- .cirrus.yml | 21 + .github/CONTRIBUTING.rst | 51 + .github/FUNDING.yml | 2 + .github/ISSUE_TEMPLATE.md | 11 + .github/SECURITY.md | 4 + .github/codecov.yml | 25 + .github/stale.yml | 18 + .github/workflows/android.yml | 69 + .github/workflows/cibuildwheel.yml | 147 + .github/workflows/docs.yml | 41 + .github/workflows/linux.yml | 340 + .github/workflows/macos.yml | 144 + .github/workflows/python.yml | 112 + .github/workflows/windows.yml | 178 + .gitignore | 86 + .gitmodules | 9 + .lgtm.yml | 20 + .pre-commit-config.yaml | 222 + AUTHORS | 25 + CMakeLists.txt | 984 + COPYING | 28 + ChangeLog | 2108 + Jamfile | 1118 + Jamroot.jam | 0 LICENSE | 183 + LibtorrentRasterbarConfig.cmake.in | 9 + Makefile | 1127 + NEWS | 1 + README.rst | 70 + appveyor.yml | 107 + bindings/CMakeLists.txt | 3 + bindings/Makefile.am | 4 + bindings/README.txt | 3 + bindings/c/Jamfile | 38 + bindings/c/library.cpp | 606 + bindings/c/libtorrent.h | 296 + bindings/c/simple_client.c | 123 + bindings/python/CMakeLists.txt | 127 + bindings/python/Jamfile | 362 + bindings/python/client.py | 381 + bindings/python/dummy_data.py | 34 + bindings/python/make_torrent.py | 61 + bindings/python/setup.py | 463 + bindings/python/setup.py.cmake.in | 15 + bindings/python/simple_client.py | 34 + bindings/python/src/alert.cpp | 1139 + bindings/python/src/boost_python.hpp | 43 + bindings/python/src/bytes.hpp | 22 + bindings/python/src/converters.cpp | 553 + bindings/python/src/create_torrent.cpp | 277 + bindings/python/src/datetime.cpp | 144 + bindings/python/src/entry.cpp | 185 + bindings/python/src/error_code.cpp | 240 + bindings/python/src/fingerprint.cpp | 34 + bindings/python/src/gil.hpp | 186 + bindings/python/src/info_hash.cpp | 41 + bindings/python/src/ip_filter.cpp | 49 + bindings/python/src/magnet_uri.cpp | 99 + bindings/python/src/module.cpp | 60 + bindings/python/src/optional.hpp | 31 + bindings/python/src/peer_info.cpp | 160 + bindings/python/src/session.cpp | 1345 + bindings/python/src/session_settings.cpp | 131 + bindings/python/src/sha1_hash.cpp | 46 + bindings/python/src/sha256_hash.cpp | 44 + bindings/python/src/string.cpp | 53 + bindings/python/src/torrent_handle.cpp | 661 + bindings/python/src/torrent_info.cpp | 514 + bindings/python/src/torrent_status.cpp | 143 + bindings/python/src/utility.cpp | 130 + bindings/python/src/version.cpp | 21 + bindings/python/test.py | 1331 + clang_tidy.jam | 105 + cmake/Modules/FindLibGcrypt.cmake | 127 + cmake/Modules/GeneratePkgConfig.cmake | 183 + .../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 + deps/asio-gnutls/Jamfile | 6 + deps/asio-gnutls/LICENSE_1_0.txt | 23 + deps/asio-gnutls/README.md | 17 + .../asio-gnutls/include/boost/asio/gnutls.hpp | 23 + .../include/boost/asio/gnutls/context.hpp | 404 + .../boost/asio/gnutls/context_base.hpp | 92 + .../include/boost/asio/gnutls/error.hpp | 88 + .../asio/gnutls/host_name_verification.hpp | 53 + .../asio/gnutls/rfc2818_verification.hpp | 28 + .../include/boost/asio/gnutls/stream.hpp | 942 + .../include/boost/asio/gnutls/stream_base.hpp | 71 + .../boost/asio/gnutls/verify_context.hpp | 40 + deps/asio-gnutls/test/gnutls/Jamfile.v2 | 55 + deps/asio-gnutls/test/gnutls/context.cpp | 65 + deps/asio-gnutls/test/gnutls/context_base.cpp | 21 + deps/asio-gnutls/test/gnutls/error.cpp | 21 + .../test/gnutls/host_name_verification.cpp | 21 + .../test/gnutls/rfc2818_verification.cpp | 21 + deps/asio-gnutls/test/gnutls/stream.cpp | 149 + deps/asio-gnutls/test/gnutls/stream_base.cpp | 21 + deps/asio-gnutls/test/unit_test.hpp | 177 + deps/try_signal/CMakeLists.txt | 6 + deps/try_signal/Jamfile | 18 + deps/try_signal/LICENSE | 29 + deps/try_signal/README.rst | 53 + deps/try_signal/appveyor.yml | 47 + deps/try_signal/example.cpp | 31 + deps/try_signal/project-root.jam | 0 deps/try_signal/signal_error_code.cpp | 209 + deps/try_signal/signal_error_code.hpp | 159 + deps/try_signal/test.cpp | 47 + deps/try_signal/try_signal.cpp | 144 + deps/try_signal/try_signal.hpp | 49 + deps/try_signal/try_signal_mingw.hpp | 78 + deps/try_signal/try_signal_msvc.hpp | 61 + deps/try_signal/try_signal_posix.hpp | 82 + ...ozilla Libtorrent Report Public Report.pdf | Bin 0 -> 747116 bytes docs/build_version.sh | 14 + docs/building.rst | 781 + docs/client_test.rst | 49 + docs/contributing.rst | 63 + docs/dht_extensions.rst | 68 + docs/dht_rss.rst | 396 + docs/dht_sec.rst | 255 + docs/dht_store.rst | 480 + docs/examples.rst | 46 + docs/extension_protocol.rst | 324 + docs/features.rst | 323 + docs/filter-rst.py | 46 + docs/fuzzing.rst | 99 + docs/gen_reference_doc.py | 1555 + docs/gen_settings_doc.py | 136 + docs/gen_stats_doc.py | 133 + docs/gen_todo.py | 166 + docs/hacking.rst | 123 + docs/header.rst | 1 + docs/hunspell/en_US.aff | 466 + docs/hunspell/en_US.dic | 62155 ++++++++++++++++ docs/hunspell/libtorrent.dic | 633 + docs/img/bitcoin.png | Bin 0 -> 2611 bytes docs/img/complete_bit_prefixes.png | Bin 0 -> 7795 bytes docs/img/cwnd.png | Bin 0 -> 18998 bytes docs/img/delays.png | Bin 0 -> 10875 bytes docs/img/hacking.diagram | 30 + docs/img/hash_distribution.png | Bin 0 -> 9035 bytes docs/img/ip_id_v4.png | Bin 0 -> 5825 bytes docs/img/ip_id_v6.png | Bin 0 -> 6416 bytes docs/img/logo-bw.svg | 112 + docs/img/logo-color-text-vertical.svg | 191 + docs/img/logo-color-text.png | Bin 0 -> 17420 bytes docs/img/logo-color-text.svg | 161 + docs/img/logo-color.svg | 155 + docs/img/logo.svg | 1309 + docs/img/our_delay_base.png | Bin 0 -> 34999 bytes docs/img/pp-acceptance-medium.png | Bin 0 -> 1159 bytes docs/img/read_disk_buffers.diagram | 16 + docs/img/screenshot.png | Bin 0 -> 501532 bytes docs/img/storage.diagram | 35 + docs/img/troubleshooting.dot | 108 + docs/img/utp_stack.diagram | 9 + docs/img/write_disk_buffers.diagram | 15 + .../include/libtorrent/add_torrent_params.hpp | 401 + docs/include/libtorrent/address.hpp | 79 + docs/include/libtorrent/alert.hpp | 333 + docs/include/libtorrent/alert_types.hpp | 2991 + docs/include/libtorrent/announce_entry.hpp | 291 + docs/include/libtorrent/assert.hpp | 130 + docs/include/libtorrent/bdecode.hpp | 466 + docs/include/libtorrent/bencode.hpp | 408 + docs/include/libtorrent/bitfield.hpp | 326 + docs/include/libtorrent/bloom_filter.hpp | 80 + .../include/libtorrent/bt_peer_connection.hpp | 519 + docs/include/libtorrent/choker.hpp | 60 + docs/include/libtorrent/client_data.hpp | 104 + docs/include/libtorrent/close_reason.hpp | 156 + docs/include/libtorrent/config.hpp | 656 + docs/include/libtorrent/copy_ptr.hpp | 68 + docs/include/libtorrent/crc32c.hpp | 46 + docs/include/libtorrent/create_torrent.hpp | 532 + docs/include/libtorrent/deadline_timer.hpp | 56 + docs/include/libtorrent/debug.hpp | 288 + docs/include/libtorrent/disabled_disk_io.hpp | 55 + .../include/libtorrent/disk_buffer_holder.hpp | 112 + docs/include/libtorrent/disk_interface.hpp | 425 + docs/include/libtorrent/disk_observer.hpp | 52 + docs/include/libtorrent/download_priority.hpp | 57 + docs/include/libtorrent/entry.hpp | 334 + docs/include/libtorrent/enum_net.hpp | 234 + docs/include/libtorrent/error.hpp | 53 + docs/include/libtorrent/error_code.hpp | 598 + docs/include/libtorrent/extensions.hpp | 548 + docs/include/libtorrent/file.hpp | 109 + docs/include/libtorrent/file_storage.hpp | 722 + docs/include/libtorrent/fingerprint.hpp | 103 + docs/include/libtorrent/flags.hpp | 141 + docs/include/libtorrent/fwd.hpp | 319 + docs/include/libtorrent/gzip.hpp | 137 + docs/include/libtorrent/hash_picker.hpp | 234 + docs/include/libtorrent/hasher.hpp | 185 + docs/include/libtorrent/hex.hpp | 97 + docs/include/libtorrent/http_connection.hpp | 257 + docs/include/libtorrent/http_parser.hpp | 168 + .../libtorrent/http_seed_connection.hpp | 114 + docs/include/libtorrent/http_stream.hpp | 230 + .../libtorrent/http_tracker_connection.hpp | 97 + docs/include/libtorrent/i2p_stream.hpp | 624 + docs/include/libtorrent/identify_client.hpp | 86 + docs/include/libtorrent/index_range.hpp | 72 + docs/include/libtorrent/info_hash.hpp | 166 + docs/include/libtorrent/io.hpp | 189 + docs/include/libtorrent/io_context.hpp | 63 + docs/include/libtorrent/io_service.hpp | 47 + docs/include/libtorrent/ip_filter.hpp | 241 + docs/include/libtorrent/ip_voter.hpp | 136 + docs/include/libtorrent/libtorrent.hpp | 168 + docs/include/libtorrent/link.hpp | 83 + docs/include/libtorrent/lsd.hpp | 95 + docs/include/libtorrent/magnet_uri.hpp | 88 + docs/include/libtorrent/mmap_disk_io.hpp | 57 + docs/include/libtorrent/mmap_storage.hpp | 227 + docs/include/libtorrent/natpmp.hpp | 219 + docs/include/libtorrent/netlink.hpp | 203 + docs/include/libtorrent/operations.hpp | 265 + docs/include/libtorrent/optional.hpp | 51 + docs/include/libtorrent/parse_url.hpp | 66 + docs/include/libtorrent/part_file.hpp | 138 + docs/include/libtorrent/pe_crypto.hpp | 162 + docs/include/libtorrent/peer.hpp | 70 + docs/include/libtorrent/peer_class.hpp | 159 + docs/include/libtorrent/peer_class_set.hpp | 70 + .../libtorrent/peer_class_type_filter.hpp | 146 + docs/include/libtorrent/peer_connection.hpp | 1255 + .../libtorrent/peer_connection_handle.hpp | 158 + .../libtorrent/peer_connection_interface.hpp | 84 + docs/include/libtorrent/peer_id.hpp | 43 + docs/include/libtorrent/peer_info.hpp | 470 + docs/include/libtorrent/peer_list.hpp | 270 + docs/include/libtorrent/peer_request.hpp | 59 + .../libtorrent/performance_counters.hpp | 500 + docs/include/libtorrent/pex_flags.hpp | 65 + docs/include/libtorrent/piece_block.hpp | 69 + .../libtorrent/piece_block_progress.hpp | 61 + docs/include/libtorrent/piece_picker.hpp | 906 + docs/include/libtorrent/platform_util.hpp | 14 + docs/include/libtorrent/portmap.hpp | 58 + docs/include/libtorrent/posix_disk_io.hpp | 55 + docs/include/libtorrent/proxy_base.hpp | 344 + docs/include/libtorrent/puff.hpp | 35 + docs/include/libtorrent/random.hpp | 85 + docs/include/libtorrent/read_resume_data.hpp | 71 + docs/include/libtorrent/request_blocks.hpp | 56 + docs/include/libtorrent/resolve_links.hpp | 97 + docs/include/libtorrent/session.hpp | 299 + docs/include/libtorrent/session_handle.hpp | 1126 + docs/include/libtorrent/session_params.hpp | 156 + docs/include/libtorrent/session_settings.hpp | 118 + docs/include/libtorrent/session_stats.hpp | 79 + docs/include/libtorrent/session_status.hpp | 238 + docs/include/libtorrent/session_types.hpp | 56 + docs/include/libtorrent/settings_pack.hpp | 2176 + docs/include/libtorrent/sha1.hpp | 43 + docs/include/libtorrent/sha1_hash.hpp | 315 + docs/include/libtorrent/sha256.hpp | 33 + docs/include/libtorrent/sliding_average.hpp | 93 + docs/include/libtorrent/socket.hpp | 298 + docs/include/libtorrent/socket_io.hpp | 146 + docs/include/libtorrent/socket_type.hpp | 65 + docs/include/libtorrent/socks5_stream.hpp | 561 + docs/include/libtorrent/span.hpp | 194 + docs/include/libtorrent/ssl.hpp | 186 + docs/include/libtorrent/ssl_stream.hpp | 354 + docs/include/libtorrent/stack_allocator.hpp | 96 + docs/include/libtorrent/stat.hpp | 287 + docs/include/libtorrent/stat_cache.hpp | 108 + docs/include/libtorrent/storage.hpp | 7 + docs/include/libtorrent/storage_defs.hpp | 150 + docs/include/libtorrent/string_util.hpp | 153 + docs/include/libtorrent/string_view.hpp | 109 + docs/include/libtorrent/tailqueue.hpp | 194 + docs/include/libtorrent/time.hpp | 92 + docs/include/libtorrent/torrent.hpp | 1798 + docs/include/libtorrent/torrent_flags.hpp | 312 + docs/include/libtorrent/torrent_handle.hpp | 1359 + docs/include/libtorrent/torrent_info.hpp | 775 + docs/include/libtorrent/torrent_peer.hpp | 287 + .../libtorrent/torrent_peer_allocator.hpp | 102 + docs/include/libtorrent/torrent_status.hpp | 608 + docs/include/libtorrent/tracker_manager.hpp | 412 + docs/include/libtorrent/truncate.hpp | 48 + docs/include/libtorrent/udp_socket.hpp | 173 + .../libtorrent/udp_tracker_connection.hpp | 133 + docs/include/libtorrent/union_endpoint.hpp | 115 + docs/include/libtorrent/units.hpp | 186 + docs/include/libtorrent/upnp.hpp | 388 + docs/include/libtorrent/utf8.hpp | 52 + docs/include/libtorrent/vector_utils.hpp | 59 + docs/include/libtorrent/version.hpp | 72 + .../libtorrent/web_connection_base.hpp | 137 + .../libtorrent/web_peer_connection.hpp | 146 + docs/include/libtorrent/write_resume_data.hpp | 58 + docs/include/libtorrent/xml_parse.hpp | 70 + docs/index.rst | 225 + docs/makefile | 231 + docs/manual.rst | 1287 + docs/plain_text_out.txt | 6569 ++ docs/projects.rst | 209 + docs/python_binding.rst | 263 + docs/security-audit.rst | 491 + docs/settings-ref.rst | 3430 + docs/single-page-ref.rst | 29321 ++++++++ docs/streaming.rst | 147 + docs/style.css | 426 + docs/stylesheet | 287 + docs/template.txt | 42 + docs/template2.txt | 45 + docs/troubleshooting.rst | 20 + docs/troubleshooting_thumb.png | Bin 0 -> 45948 bytes docs/tuning.rst | 344 + docs/tutorial.rst | 305 + docs/udp_tracker_protocol.rst | 320 + docs/upgrade_to_1.2.rst | 164 + docs/upgrade_to_2.0.rst | 336 + docs/utp.rst | 345 + examples/CMakeLists.txt | 27 + examples/Jamfile | 60 + examples/Makefile.am | 35 + examples/bt-get.cpp | 82 + examples/bt-get2.cpp | 179 + examples/bt-get3.cpp | 213 + examples/client_test.cpp | 2192 + examples/cmake/FindLibtorrentRasterbar.cmake | 190 + examples/connection_tester.cpp | 1217 + examples/custom_storage.cpp | 328 + examples/dump_bdecode.cpp | 120 + examples/dump_torrent.cpp | 193 + examples/make_torrent.cpp | 319 + examples/print.cpp | 553 + examples/print.hpp | 53 + examples/run_benchmarks.py | 438 + examples/session_view.cpp | 163 + examples/session_view.hpp | 103 + examples/simple_client.cpp | 63 + examples/stats_counters.cpp | 50 + examples/torrent2magnet.cpp | 94 + examples/torrent_view.cpp | 481 + examples/torrent_view.hpp | 112 + examples/upnp_test.cpp | 108 + fuzzers/Jamfile | 102 + fuzzers/LICENSE | 29 + fuzzers/README.rst | 64 + fuzzers/main.cpp | 56 + fuzzers/minimize.sh | 18 + fuzzers/run.sh | 13 + fuzzers/src/add_torrent.cpp | 243 + fuzzers/src/base32decode.cpp | 40 + fuzzers/src/base32encode.cpp | 40 + fuzzers/src/base64encode.cpp | 40 + fuzzers/src/bdecode_node.cpp | 41 + fuzzers/src/convert_from_native.cpp | 40 + fuzzers/src/convert_to_native.cpp | 40 + fuzzers/src/dht_node.cpp | 102 + fuzzers/src/escape_path.cpp | 40 + fuzzers/src/escape_string.cpp | 40 + fuzzers/src/file_storage_add_file.cpp | 45 + fuzzers/src/gzip.cpp | 43 + fuzzers/src/http_parser.cpp | 60 + fuzzers/src/http_tracker.cpp | 49 + fuzzers/src/idna.cpp | 42 + fuzzers/src/parse_int.cpp | 41 + fuzzers/src/parse_magnet_uri.cpp | 45 + fuzzers/src/parse_url.cpp | 43 + fuzzers/src/peer_conn.cpp | 222 + fuzzers/src/read_bits.hpp | 65 + fuzzers/src/resume_data.cpp | 45 + fuzzers/src/sanitize_path.cpp | 41 + fuzzers/src/session_params.cpp | 44 + fuzzers/src/torrent_info.cpp | 41 + fuzzers/src/upnp.cpp | 45 + fuzzers/src/utf8_codepoint.cpp | 43 + fuzzers/src/utp.cpp | 70 + fuzzers/src/verify_encoding.cpp | 42 + fuzzers/tools/generate_initial_corpus.py | 214 + fuzzers/tools/unify_corpus_names.py | 24 + include/libtorrent/add_torrent_params.hpp | 401 + include/libtorrent/address.hpp | 79 + include/libtorrent/alert.hpp | 333 + include/libtorrent/alert_types.hpp | 2991 + include/libtorrent/announce_entry.hpp | 291 + include/libtorrent/assert.hpp | 130 + include/libtorrent/aux_/alert_manager.hpp | 180 + include/libtorrent/aux_/aligned_storage.hpp | 61 + include/libtorrent/aux_/aligned_union.hpp | 72 + include/libtorrent/aux_/alloca.hpp | 117 + .../libtorrent/aux_/allocating_handler.hpp | 362 + include/libtorrent/aux_/announce_entry.hpp | 216 + include/libtorrent/aux_/apply_pad_files.hpp | 105 + include/libtorrent/aux_/array.hpp | 48 + include/libtorrent/aux_/bandwidth_limit.hpp | 105 + include/libtorrent/aux_/bandwidth_manager.hpp | 94 + .../libtorrent/aux_/bandwidth_queue_entry.hpp | 77 + include/libtorrent/aux_/bandwidth_socket.hpp | 51 + include/libtorrent/aux_/bind_to_device.hpp | 137 + include/libtorrent/aux_/buffer.hpp | 170 + include/libtorrent/aux_/byteswap.hpp | 100 + include/libtorrent/aux_/chained_buffer.hpp | 235 + include/libtorrent/aux_/container_wrapper.hpp | 136 + include/libtorrent/aux_/cpuid.hpp | 47 + include/libtorrent/aux_/deferred_handler.hpp | 81 + include/libtorrent/aux_/deprecated.hpp | 68 + include/libtorrent/aux_/deque.hpp | 47 + include/libtorrent/aux_/dev_random.hpp | 80 + include/libtorrent/aux_/directory.hpp | 79 + .../disable_deprecation_warnings_push.hpp | 51 + .../libtorrent/aux_/disable_warnings_pop.hpp | 43 + .../libtorrent/aux_/disable_warnings_push.hpp | 113 + include/libtorrent/aux_/disk_buffer_pool.hpp | 132 + .../libtorrent/aux_/disk_io_thread_pool.hpp | 150 + include/libtorrent/aux_/disk_job_fence.hpp | 118 + include/libtorrent/aux_/disk_job_pool.hpp | 75 + include/libtorrent/aux_/ed25519.hpp | 18 + include/libtorrent/aux_/escape_string.hpp | 104 + include/libtorrent/aux_/export.hpp | 153 + include/libtorrent/aux_/ffs.hpp | 71 + include/libtorrent/aux_/file_pointer.hpp | 78 + include/libtorrent/aux_/file_progress.hpp | 109 + include/libtorrent/aux_/file_view_pool.hpp | 186 + include/libtorrent/aux_/generate_peer_id.hpp | 48 + include/libtorrent/aux_/has_block.hpp | 57 + include/libtorrent/aux_/hasher512.hpp | 134 + .../libtorrent/aux_/heterogeneous_queue.hpp | 260 + .../aux_/instantiate_connection.hpp | 58 + include/libtorrent/aux_/invariant_check.hpp | 86 + include/libtorrent/aux_/io.hpp | 180 + include/libtorrent/aux_/ip_helpers.hpp | 68 + include/libtorrent/aux_/ip_notifier.hpp | 59 + include/libtorrent/aux_/keepalive.hpp | 102 + .../libtorrent/aux_/listen_socket_handle.hpp | 92 + include/libtorrent/aux_/lsd.hpp | 55 + include/libtorrent/aux_/merkle.hpp | 162 + include/libtorrent/aux_/merkle_tree.hpp | 226 + include/libtorrent/aux_/mmap.hpp | 210 + include/libtorrent/aux_/mmap_disk_job.hpp | 233 + include/libtorrent/aux_/noexcept_movable.hpp | 112 + include/libtorrent/aux_/numeric_cast.hpp | 70 + include/libtorrent/aux_/open_mode.hpp | 62 + include/libtorrent/aux_/packet_buffer.hpp | 122 + include/libtorrent/aux_/packet_pool.hpp | 226 + include/libtorrent/aux_/path.hpp | 187 + .../libtorrent/aux_/polymorphic_socket.hpp | 188 + include/libtorrent/aux_/pool.hpp | 60 + include/libtorrent/aux_/portmap.hpp | 105 + include/libtorrent/aux_/posix_part_file.hpp | 139 + include/libtorrent/aux_/posix_storage.hpp | 122 + include/libtorrent/aux_/proxy_settings.hpp | 93 + include/libtorrent/aux_/range.hpp | 70 + include/libtorrent/aux_/receive_buffer.hpp | 230 + include/libtorrent/aux_/resolver.hpp | 102 + .../libtorrent/aux_/resolver_interface.hpp | 79 + include/libtorrent/aux_/route.h | 478 + include/libtorrent/aux_/scope_end.hpp | 64 + include/libtorrent/aux_/session_call.hpp | 52 + include/libtorrent/aux_/session_impl.hpp | 1392 + include/libtorrent/aux_/session_interface.hpp | 316 + include/libtorrent/aux_/session_settings.hpp | 190 + .../libtorrent/aux_/session_udp_sockets.hpp | 78 + include/libtorrent/aux_/set_socket_buffer.hpp | 92 + include/libtorrent/aux_/set_traffic_class.hpp | 60 + include/libtorrent/aux_/sha512.hpp | 33 + include/libtorrent/aux_/socket_type.hpp | 100 + include/libtorrent/aux_/storage_free_list.hpp | 73 + include/libtorrent/aux_/storage_utils.hpp | 127 + include/libtorrent/aux_/store_buffer.hpp | 150 + include/libtorrent/aux_/string_ptr.hpp | 76 + include/libtorrent/aux_/strview_less.hpp | 52 + include/libtorrent/aux_/suggest_piece.hpp | 128 + include/libtorrent/aux_/throw.hpp | 54 + include/libtorrent/aux_/time.hpp | 48 + include/libtorrent/aux_/timestamp_history.hpp | 88 + include/libtorrent/aux_/torrent_impl.hpp | 80 + include/libtorrent/aux_/torrent_list.hpp | 260 + include/libtorrent/aux_/unique_ptr.hpp | 66 + .../libtorrent/aux_/utp_socket_manager.hpp | 202 + include/libtorrent/aux_/utp_stream.hpp | 974 + include/libtorrent/aux_/vector.hpp | 48 + include/libtorrent/aux_/win_cng.hpp | 208 + .../libtorrent/aux_/win_crypto_provider.hpp | 148 + include/libtorrent/aux_/win_util.hpp | 108 + include/libtorrent/aux_/windows.hpp | 51 + include/libtorrent/bdecode.hpp | 466 + include/libtorrent/bencode.hpp | 408 + include/libtorrent/bitfield.hpp | 326 + include/libtorrent/bloom_filter.hpp | 80 + include/libtorrent/bt_peer_connection.hpp | 519 + include/libtorrent/choker.hpp | 60 + include/libtorrent/client_data.hpp | 104 + include/libtorrent/close_reason.hpp | 156 + include/libtorrent/config.hpp | 656 + include/libtorrent/copy_ptr.hpp | 68 + include/libtorrent/crc32c.hpp | 46 + include/libtorrent/create_torrent.hpp | 532 + include/libtorrent/deadline_timer.hpp | 56 + include/libtorrent/debug.hpp | 288 + include/libtorrent/disabled_disk_io.hpp | 55 + include/libtorrent/disk_buffer_holder.hpp | 112 + include/libtorrent/disk_interface.hpp | 425 + include/libtorrent/disk_observer.hpp | 52 + include/libtorrent/download_priority.hpp | 57 + include/libtorrent/entry.hpp | 334 + include/libtorrent/enum_net.hpp | 234 + include/libtorrent/error.hpp | 53 + include/libtorrent/error_code.hpp | 598 + include/libtorrent/extensions.hpp | 548 + include/libtorrent/extensions/smart_ban.hpp | 61 + include/libtorrent/extensions/ut_metadata.hpp | 63 + include/libtorrent/extensions/ut_pex.hpp | 64 + include/libtorrent/file.hpp | 109 + include/libtorrent/file_storage.hpp | 722 + include/libtorrent/fingerprint.hpp | 103 + include/libtorrent/flags.hpp | 141 + include/libtorrent/fwd.hpp | 319 + include/libtorrent/gzip.hpp | 137 + include/libtorrent/hash_picker.hpp | 234 + include/libtorrent/hasher.hpp | 185 + include/libtorrent/hex.hpp | 97 + include/libtorrent/http_connection.hpp | 257 + include/libtorrent/http_parser.hpp | 168 + include/libtorrent/http_seed_connection.hpp | 114 + include/libtorrent/http_stream.hpp | 230 + .../libtorrent/http_tracker_connection.hpp | 97 + include/libtorrent/i2p_stream.hpp | 624 + include/libtorrent/identify_client.hpp | 86 + include/libtorrent/index_range.hpp | 72 + include/libtorrent/info_hash.hpp | 166 + include/libtorrent/io.hpp | 189 + include/libtorrent/io_context.hpp | 63 + include/libtorrent/io_service.hpp | 47 + include/libtorrent/ip_filter.hpp | 241 + include/libtorrent/ip_voter.hpp | 136 + .../libtorrent/kademlia/announce_flags.hpp | 63 + include/libtorrent/kademlia/dht_observer.hpp | 100 + include/libtorrent/kademlia/dht_settings.hpp | 184 + include/libtorrent/kademlia/dht_state.hpp | 81 + include/libtorrent/kademlia/dht_storage.hpp | 245 + include/libtorrent/kademlia/dht_tracker.hpp | 230 + .../libtorrent/kademlia/direct_request.hpp | 98 + include/libtorrent/kademlia/dos_blocker.hpp | 94 + include/libtorrent/kademlia/ed25519.hpp | 105 + include/libtorrent/kademlia/find_data.hpp | 91 + include/libtorrent/kademlia/get_item.hpp | 98 + include/libtorrent/kademlia/get_peers.hpp | 115 + include/libtorrent/kademlia/io.hpp | 65 + include/libtorrent/kademlia/item.hpp | 130 + include/libtorrent/kademlia/msg.hpp | 103 + include/libtorrent/kademlia/node.hpp | 305 + include/libtorrent/kademlia/node_entry.hpp | 97 + include/libtorrent/kademlia/node_id.hpp | 77 + include/libtorrent/kademlia/observer.hpp | 156 + include/libtorrent/kademlia/put_data.hpp | 93 + include/libtorrent/kademlia/refresh.hpp | 67 + include/libtorrent/kademlia/routing_table.hpp | 345 + include/libtorrent/kademlia/rpc_manager.hpp | 147 + .../libtorrent/kademlia/sample_infohashes.hpp | 84 + .../kademlia/traversal_algorithm.hpp | 176 + include/libtorrent/kademlia/types.hpp | 100 + include/libtorrent/libtorrent.hpp | 168 + include/libtorrent/link.hpp | 83 + include/libtorrent/lsd.hpp | 95 + include/libtorrent/magnet_uri.hpp | 88 + include/libtorrent/mmap_disk_io.hpp | 57 + include/libtorrent/mmap_storage.hpp | 227 + include/libtorrent/natpmp.hpp | 219 + include/libtorrent/netlink.hpp | 203 + include/libtorrent/operations.hpp | 265 + include/libtorrent/optional.hpp | 51 + include/libtorrent/parse_url.hpp | 66 + include/libtorrent/part_file.hpp | 138 + include/libtorrent/pe_crypto.hpp | 162 + include/libtorrent/peer.hpp | 70 + include/libtorrent/peer_class.hpp | 159 + include/libtorrent/peer_class_set.hpp | 70 + include/libtorrent/peer_class_type_filter.hpp | 146 + include/libtorrent/peer_connection.hpp | 1255 + include/libtorrent/peer_connection_handle.hpp | 158 + .../libtorrent/peer_connection_interface.hpp | 84 + include/libtorrent/peer_id.hpp | 43 + include/libtorrent/peer_info.hpp | 470 + include/libtorrent/peer_list.hpp | 270 + include/libtorrent/peer_request.hpp | 59 + include/libtorrent/performance_counters.hpp | 500 + include/libtorrent/pex_flags.hpp | 65 + include/libtorrent/piece_block.hpp | 69 + include/libtorrent/piece_block_progress.hpp | 61 + include/libtorrent/piece_picker.hpp | 906 + include/libtorrent/platform_util.hpp | 14 + include/libtorrent/portmap.hpp | 58 + include/libtorrent/posix_disk_io.hpp | 55 + include/libtorrent/proxy_base.hpp | 344 + include/libtorrent/puff.hpp | 35 + include/libtorrent/random.hpp | 85 + include/libtorrent/read_resume_data.hpp | 71 + include/libtorrent/request_blocks.hpp | 56 + include/libtorrent/resolve_links.hpp | 97 + include/libtorrent/session.hpp | 299 + include/libtorrent/session_handle.hpp | 1126 + include/libtorrent/session_params.hpp | 156 + include/libtorrent/session_settings.hpp | 118 + include/libtorrent/session_stats.hpp | 79 + include/libtorrent/session_status.hpp | 238 + include/libtorrent/session_types.hpp | 56 + include/libtorrent/settings_pack.hpp | 2176 + include/libtorrent/sha1.hpp | 43 + include/libtorrent/sha1_hash.hpp | 315 + include/libtorrent/sha256.hpp | 33 + include/libtorrent/sliding_average.hpp | 93 + include/libtorrent/socket.hpp | 298 + include/libtorrent/socket_io.hpp | 146 + include/libtorrent/socket_type.hpp | 65 + include/libtorrent/socks5_stream.hpp | 561 + include/libtorrent/span.hpp | 194 + include/libtorrent/ssl.hpp | 186 + include/libtorrent/ssl_stream.hpp | 354 + include/libtorrent/stack_allocator.hpp | 96 + include/libtorrent/stat.hpp | 287 + include/libtorrent/stat_cache.hpp | 108 + include/libtorrent/storage.hpp | 7 + include/libtorrent/storage_defs.hpp | 150 + include/libtorrent/string_util.hpp | 153 + include/libtorrent/string_view.hpp | 109 + include/libtorrent/tailqueue.hpp | 194 + include/libtorrent/time.hpp | 92 + include/libtorrent/torrent.hpp | 1798 + include/libtorrent/torrent_flags.hpp | 312 + include/libtorrent/torrent_handle.hpp | 1359 + include/libtorrent/torrent_info.hpp | 775 + include/libtorrent/torrent_peer.hpp | 287 + include/libtorrent/torrent_peer_allocator.hpp | 102 + include/libtorrent/torrent_status.hpp | 608 + include/libtorrent/tracker_manager.hpp | 412 + include/libtorrent/truncate.hpp | 48 + include/libtorrent/udp_socket.hpp | 173 + include/libtorrent/udp_tracker_connection.hpp | 133 + include/libtorrent/union_endpoint.hpp | 115 + include/libtorrent/units.hpp | 186 + include/libtorrent/upnp.hpp | 388 + include/libtorrent/utf8.hpp | 52 + include/libtorrent/vector_utils.hpp | 59 + include/libtorrent/version.hpp | 72 + include/libtorrent/web_connection_base.hpp | 137 + include/libtorrent/web_peer_connection.hpp | 146 + include/libtorrent/write_resume_data.hpp | 58 + include/libtorrent/xml_parse.hpp | 70 + project-config.jam | 0 pyproject.toml | 89 + setup.cfg | 15 + setup.py | 6 + simulation/Jamfile | 65 + simulation/create_torrent.cpp | 73 + simulation/create_torrent.hpp | 45 + simulation/disk_io.cpp | 719 + simulation/disk_io.hpp | 131 + simulation/fake_peer.hpp | 473 + simulation/make_proxy_settings.hpp | 57 + simulation/setup_dht.cpp | 318 + simulation/setup_dht.hpp | 74 + simulation/setup_swarm.cpp | 428 + simulation/setup_swarm.hpp | 101 + simulation/test_auto_manage.cpp | 917 + simulation/test_checking.cpp | 225 + simulation/test_dht.cpp | 331 + simulation/test_dht_bootstrap.cpp | 112 + simulation/test_dht_rate_limit.cpp | 271 + simulation/test_dht_storage.cpp | 219 + simulation/test_error_handling.cpp | 250 + simulation/test_fast_extensions.cpp | 360 + simulation/test_file_pool.cpp | 138 + simulation/test_http_connection.cpp | 663 + simulation/test_ip_filter.cpp | 239 + simulation/test_metadata_extension.cpp | 250 + simulation/test_optimistic_unchoke.cpp | 173 + simulation/test_pause.cpp | 335 + simulation/test_pe_crypto.cpp | 197 + simulation/test_peer_connection.cpp | 309 + simulation/test_save_resume.cpp | 88 + simulation/test_session.cpp | 254 + simulation/test_socks5.cpp | 283 + simulation/test_super_seeding.cpp | 78 + simulation/test_swarm.cpp | 1032 + simulation/test_thread_pool.cpp | 216 + simulation/test_timeout.cpp | 308 + simulation/test_torrent_status.cpp | 570 + simulation/test_tracker.cpp | 1839 + simulation/test_transfer.cpp | 547 + simulation/test_transfer_matrix.cpp | 97 + simulation/test_utp.cpp | 232 + simulation/test_web_seed.cpp | 869 + simulation/transfer_sim.cpp | 53 + simulation/transfer_sim.hpp | 223 + simulation/utils.cpp | 256 + simulation/utils.hpp | 104 + src/add_torrent_params.cpp | 99 + src/alert.cpp | 3175 + src/alert_manager.cpp | 148 + src/announce_entry.cpp | 289 + src/assert.cpp | 407 + src/bandwidth_limit.cpp | 110 + src/bandwidth_manager.cpp | 225 + src/bandwidth_queue_entry.cpp | 81 + src/bdecode.cpp | 1176 + src/bitfield.cpp | 236 + src/bloom_filter.cpp | 77 + src/bt_peer_connection.cpp | 3803 + src/chained_buffer.cpp | 174 + src/choker.cpp | 311 + src/close_reason.cpp | 167 + src/copy_file.cpp | 431 + src/cpuid.cpp | 162 + src/crc32c.cpp | 151 + src/create_torrent.cpp | 998 + src/directory.cpp | 126 + src/disabled_disk_io.cpp | 206 + src/disk_buffer_holder.cpp | 65 + src/disk_buffer_pool.cpp | 235 + src/disk_interface.cpp | 44 + src/disk_io_thread_pool.cpp | 209 + src/disk_job_fence.cpp | 220 + src/disk_job_pool.cpp | 111 + src/ed25519/LICENSE | 22 + src/ed25519/add_scalar.cpp | 75 + src/ed25519/fe.cpp | 1490 + src/ed25519/fe.h | 41 + src/ed25519/fixedint.h | 10 + src/ed25519/ge.cpp | 472 + src/ed25519/ge.h | 74 + src/ed25519/hasher512.cpp | 152 + src/ed25519/key_exchange.cpp | 88 + src/ed25519/keypair.cpp | 24 + src/ed25519/precomp_data.h | 1392 + src/ed25519/sc.cpp | 816 + src/ed25519/sc.h | 13 + src/ed25519/sha512.cpp | 291 + src/ed25519/sign.cpp | 37 + src/ed25519/verify.cpp | 84 + src/entry.cpp | 774 + src/enum_net.cpp | 1538 + src/error_code.cpp | 371 + src/escape_string.cpp | 586 + src/ffs.cpp | 185 + src/file.cpp | 331 + src/file_progress.cpp | 208 + src/file_storage.cpp | 1579 + src/file_view_pool.cpp | 306 + src/fingerprint.cpp | 103 + src/generate_peer_id.cpp | 56 + src/gzip.cpp | 265 + src/hash_picker.cpp | 427 + src/hasher.cpp | 283 + src/hex.cpp | 106 + src/http_connection.cpp | 897 + src/http_parser.cpp | 648 + src/http_seed_connection.cpp | 487 + src/http_tracker_connection.cpp | 676 + src/i2p_stream.cpp | 141 + src/identify_client.cpp | 440 + src/instantiate_connection.cpp | 154 + src/ip_filter.cpp | 285 + src/ip_helpers.cpp | 124 + src/ip_notifier.cpp | 449 + src/ip_voter.cpp | 194 + src/kademlia/dht_settings.cpp | 116 + src/kademlia/dht_state.cpp | 135 + src/kademlia/dht_storage.cpp | 634 + src/kademlia/dht_tracker.cpp | 737 + src/kademlia/dos_blocker.cpp | 114 + src/kademlia/ed25519.cpp | 127 + src/kademlia/find_data.cpp | 197 + src/kademlia/get_item.cpp | 222 + src/kademlia/get_peers.cpp | 332 + src/kademlia/item.cpp | 212 + src/kademlia/msg.cpp | 138 + src/kademlia/node.cpp | 1251 + src/kademlia/node_entry.cpp | 65 + src/kademlia/node_id.cpp | 215 + src/kademlia/put_data.cpp | 118 + src/kademlia/refresh.cpp | 107 + src/kademlia/routing_table.cpp | 1239 + src/kademlia/rpc_manager.cpp | 522 + src/kademlia/sample_infohashes.cpp | 161 + src/kademlia/traversal_algorithm.cpp | 675 + src/listen_socket_handle.cpp | 75 + src/lsd.cpp | 340 + src/magnet_uri.cpp | 391 + src/merkle.cpp | 454 + src/merkle_tree.cpp | 1079 + src/mmap.cpp | 742 + src/mmap_disk_io.cpp | 1858 + src/mmap_disk_job.cpp | 136 + src/mmap_storage.cpp | 947 + src/natpmp.cpp | 929 + src/packet_buffer.cpp | 195 + src/parse_url.cpp | 216 + src/part_file.cpp | 447 + src/path.cpp | 973 + src/pe_crypto.cpp | 413 + src/peer_class.cpp | 126 + src/peer_class_set.cpp | 73 + src/peer_connection.cpp | 6796 ++ src/peer_connection_handle.cpp | 357 + src/peer_info.cpp | 89 + src/peer_list.cpp | 1335 + src/performance_counters.cpp | 160 + src/piece_picker.cpp | 3933 + src/platform_util.cpp | 132 + src/posix_disk_io.cpp | 406 + src/posix_part_file.cpp | 480 + src/posix_storage.cpp | 554 + src/proxy_base.cpp | 47 + src/proxy_settings.cpp | 67 + src/puff.cpp | 844 + src/random.cpp | 198 + src/read_resume_data.cpp | 444 + src/receive_buffer.cpp | 341 + src/request_blocks.cpp | 310 + src/resolve_links.cpp | 188 + src/resolver.cpp | 176 + src/session.cpp | 551 + src/session_call.cpp | 82 + src/session_handle.cpp | 1267 + src/session_impl.cpp | 7295 ++ src/session_params.cpp | 295 + src/session_settings.cpp | 65 + src/session_stats.cpp | 599 + src/settings_pack.cpp | 848 + src/sha1.cpp | 329 + src/sha1_hash.cpp | 179 + src/sha256.cpp | 180 + src/smart_ban.cpp | 329 + src/socket_io.cpp | 167 + src/socket_type.cpp | 269 + src/socks5_stream.cpp | 81 + src/ssl.cpp | 212 + src/stack_allocator.cpp | 143 + src/stat.cpp | 46 + src/stat_cache.cpp | 145 + src/storage_utils.cpp | 807 + src/string_util.cpp | 412 + src/time.cpp | 40 + src/timestamp_history.cpp | 108 + src/torrent.cpp | 11995 +++ src/torrent_handle.cpp | 922 + src/torrent_info.cpp | 1898 + src/torrent_peer.cpp | 286 + src/torrent_peer_allocator.cpp | 117 + src/torrent_status.cpp | 55 + src/tracker_manager.cpp | 501 + src/truncate.cpp | 177 + src/udp_socket.cpp | 1000 + src/udp_tracker_connection.cpp | 780 + src/upnp.cpp | 1676 + src/ut_metadata.cpp | 635 + src/ut_pex.cpp | 644 + src/utf8.cpp | 177 + src/utp_socket_manager.cpp | 332 + src/utp_stream.cpp | 3343 + src/version.cpp | 42 + src/web_connection_base.cpp | 213 + src/web_peer_connection.cpp | 1243 + src/write_resume_data.cpp | 390 + src/xml_parse.cpp | 171 + test/CMakeLists.txt | 62 + test/Jamfile | 320 + test/bittorrent_peer.cpp | 563 + test/bittorrent_peer.hpp | 110 + test/broadcast_socket.cpp | 249 + test/broadcast_socket.hpp | 143 + test/corrupt.gz | Bin 0 -> 296 bytes test/dht_server.cpp | 183 + test/dht_server.hpp | 42 + test/enum_if.cpp | 134 + test/http_proxy.py | 550 + test/invalid1.gz | Bin 0 -> 27 bytes test/main.cpp | 601 + test/make_torrent.cpp | 214 + test/make_torrent.hpp | 69 + 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 | 170 + 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 | 93 + test/settings.hpp | 37 + test/setup_transfer.cpp | 1240 + test/setup_transfer.hpp | 129 + 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 | 245 + test/swarm_suite.hpp | 52 + test/test.cpp | 102 + test/test.hpp | 197 + test/test_add_torrent.cpp | 305 + test/test_alert_manager.cpp | 364 + test/test_alert_types.cpp | 355 + test/test_alloca.cpp | 82 + test/test_apply_pad.cpp | 175 + test/test_auto_unchoke.cpp | 165 + test/test_bandwidth_limiter.cpp | 519 + test/test_bdecode.cpp | 1341 + test/test_bencoding.cpp | 274 + test/test_bitfield.cpp | 431 + test/test_bloom_filter.cpp | 137 + test/test_buffer.cpp | 311 + test/test_checking.cpp | 435 + test/test_copy_file.cpp | 232 + test/test_crc32.cpp | 71 + test/test_create_torrent.cpp | 555 + test/test_dht.cpp | 4089 + test/test_dht_storage.cpp | 511 + test/test_direct_dht.cpp | 150 + test/test_dos_blocker.cpp | 105 + test/test_ed25519.cpp | 290 + test/test_enum_net.cpp | 278 + test/test_fast_extension.cpp | 1136 + test/test_fence.cpp | 233 + test/test_ffs.cpp | 139 + test/test_file.cpp | 616 + test/test_file_progress.cpp | 180 + test/test_file_storage.cpp | 1188 + test/test_flags.cpp | 235 + test/test_generate_peer_id.cpp | 56 + test/test_gzip.cpp | 101 + test/test_hash_picker.cpp | 626 + test/test_hasher.cpp | 146 + test/test_hasher512.cpp | 92 + test/test_heterogeneous_queue.cpp | 339 + test/test_http_connection.cpp | 250 + test/test_http_parser.cpp | 914 + test/test_identify_client.cpp | 48 + test/test_info_hash.cpp | 164 + test/test_io.cpp | 291 + test/test_ip_filter.cpp | 260 + test/test_ip_voter.cpp | 223 + test/test_listen_socket.cpp | 538 + test/test_lsd.cpp | 131 + test/test_magnet.cpp | 672 + test/test_merkle.cpp | 1317 + test/test_merkle_tree.cpp | 938 + test/test_mmap.cpp | 119 + test/test_natpmp.cpp | 169 + test/test_packet_buffer.cpp | 186 + test/test_part_file.cpp | 258 + test/test_pe_crypto.cpp | 163 + test/test_peer_classes.cpp | 151 + test/test_peer_list.cpp | 976 + test/test_peer_priority.cpp | 99 + test/test_piece_picker.cpp | 2809 + test/test_primitives.cpp | 256 + test/test_priority.cpp | 713 + test/test_privacy.cpp | 346 + test/test_read_piece.cpp | 161 + test/test_read_resume.cpp | 398 + test/test_receive_buffer.cpp | 258 + test/test_recheck.cpp | 121 + test/test_remap_files.cpp | 225 + test/test_remove_torrent.cpp | 224 + test/test_resolve_links.cpp | 235 + test/test_resume.cpp | 1842 + test/test_session.cpp | 582 + test/test_session_params.cpp | 287 + test/test_settings_pack.cpp | 321 + test/test_sha1_hash.cpp | 147 + test/test_similar_torrent.cpp | 347 + test/test_sliding_average.cpp | 118 + test/test_socket_io.cpp | 218 + test/test_span.cpp | 153 + test/test_ssl.cpp | 635 + test/test_stack_allocator.cpp | 142 + test/test_stat_cache.cpp | 88 + test/test_storage.cpp | 1827 + test/test_store_buffer.cpp | 186 + test/test_string.cpp | 566 + test/test_tailqueue.cpp | 170 + test/test_threads.cpp | 129 + test/test_time.cpp | 105 + test/test_time_critical.cpp | 61 + test/test_timestamp_history.cpp | 57 + test/test_torrent.cpp | 896 + test/test_torrent_info.cpp | 1362 + test/test_torrent_list.cpp | 249 + 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-files-1.torrent | Bin 0 -> 927 bytes test/test_torrents/empty-files-2.torrent | Bin 0 -> 927 bytes test/test_torrents/empty-files-3.torrent | Bin 0 -> 927 bytes test/test_torrents/empty-files-4.torrent | Bin 0 -> 927 bytes test/test_torrents/empty-files-5.torrent | Bin 0 -> 927 bytes 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_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_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/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/v2.torrent | 2 + .../v2_bad_file_alignment.torrent | Bin 0 -> 156906 bytes test/test_torrents/v2_deep_recursion.torrent | 1 + test/test_torrents/v2_empty_file.torrent | Bin 0 -> 13529 bytes test/test_torrents/v2_hybrid.torrent | Bin 0 -> 91581 bytes .../v2_incomplete_piece_layer.torrent | Bin 0 -> 64533 bytes test/test_torrents/v2_invalid_file.torrent | 2 + .../test_torrents/v2_invalid_filename.torrent | 17 + .../test_torrents/v2_invalid_pad_file.torrent | 17 + .../v2_invalid_piece_layer.torrent | 1 + .../v2_invalid_piece_layer_size.torrent | 33 + .../v2_invalid_root_hash.torrent | Bin 0 -> 3135 bytes test/test_torrents/v2_large_file.torrent | 17 + test/test_torrents/v2_large_offset.torrent | 17 + .../v2_mismatching_metadata.torrent | 17 + ..._missing_file_root_invalid_symlink.torrent | Bin 0 -> 96517 bytes test/test_torrents/v2_multipiece_file.torrent | 17 + test/test_torrents/v2_multiple_files.torrent | Bin 0 -> 96605 bytes test/test_torrents/v2_no_piece_layers.torrent | 1 + test/test_torrents/v2_no_power2_piece.torrent | 1 + .../v2_non_multiple_piece_layer.torrent | 17 + test/test_torrents/v2_only.torrent | 17 + .../test_torrents/v2_overlong_integer.torrent | 2 + .../v2_piece_layer_invalid_file_hash.torrent | 17 + test/test_torrents/v2_piece_size.torrent | 1 + test/test_torrents/v2_symlinks.torrent | Bin 0 -> 3290 bytes test/test_torrents/v2_unordered_files.torrent | 2 + test/test_torrents/v2_zero_root.torrent | Bin 0 -> 767 bytes test/test_torrents/v2_zero_root_small.torrent | Bin 0 -> 214 bytes test/test_torrents/whitespace_url.torrent | 1 + test/test_torrents/zero.torrent | 1 + test/test_torrents/zero2.torrent | 1 + test/test_tracker.cpp | 734 + test/test_transfer.cpp | 382 + test/test_truncate.cpp | 107 + test/test_upnp.cpp | 363 + test/test_url_seed.cpp | 67 + test/test_utf8.cpp | 147 + test/test_utils.cpp | 149 + test/test_utils.hpp | 89 + test/test_utp.cpp | 169 + 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 | 99 + 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 | 493 + test/udp_tracker.cpp | 260 + test/udp_tracker.hpp | 44 + test/utf8_test.txt | 150 + test/valgrind_suppressions.txt | 8 + test/web_seed_suite.cpp | 403 + test/web_seed_suite.hpp | 43 + test/web_server.py | 226 + test/zeroes.gz | Bin 0 -> 538 bytes tools/CMakeLists.txt | 8 + tools/Jamfile | 47 + tools/benchmark_checking.py | 123 + tools/cibuildwheel/manylinux/build-openssl.sh | 45 + tools/cibuildwheel/manylinux/build_utils.sh | 63 + .../cibuildwheel/manylinux/openssl-version.sh | 5 + tools/cibuildwheel/setup_boost.sh | 28 + .../cibuildwheel/setup_ccache_on_manylinux.sh | 23 + tools/cibuildwheel/setup_openssl.sh | 17 + tools/clean.py | 88 + tools/copyright.py | 137 + tools/dht_flood.py | 78 + tools/dht_put.cpp | 430 + tools/dht_sample.cpp | 201 + tools/disk_io_stress_test.cpp | 317 + tools/gen_convenience_header.py | 22 + tools/gen_fwd.py | 140 + tools/libtorrent_lldb.py | 184 + tools/parse_dht_log.py | 341 + tools/parse_dht_rtt.py | 55 + tools/parse_dht_stats.py | 64 + tools/parse_lookup_log.py | 131 + tools/parse_peer_log.py | 75 + tools/parse_sample.py | 162 + tools/parse_session_stats.py | 650 + tools/parse_utp_log.py | 417 + tools/run_benchmark.py | 145 + tools/run_tests.sh | 21 + tools/sanitizer-blacklist.txt | 4 + tools/session_log_alerts.cpp | 72 + tools/set_version.py | 80 + tools/test_coverage.sh | 137 + tools/update_copyright.py | 56 + tools/vmstat.py | 371 + 1162 files changed, 413769 insertions(+) create mode 100644 .cirrus.yml create mode 100644 .github/CONTRIBUTING.rst create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/SECURITY.md create mode 100644 .github/codecov.yml create mode 100644 .github/stale.yml create mode 100644 .github/workflows/android.yml create mode 100644 .github/workflows/cibuildwheel.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/linux.yml create mode 100644 .github/workflows/macos.yml create mode 100644 .github/workflows/python.yml create mode 100644 .github/workflows/windows.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .lgtm.yml create mode 100644 .pre-commit-config.yaml 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 create mode 100644 NEWS create mode 100644 README.rst create mode 100644 appveyor.yml create mode 100644 bindings/CMakeLists.txt create mode 100644 bindings/Makefile.am create mode 100644 bindings/README.txt create mode 100644 bindings/c/Jamfile create mode 100644 bindings/c/library.cpp create mode 100644 bindings/c/libtorrent.h create mode 100644 bindings/c/simple_client.c create mode 100644 bindings/python/CMakeLists.txt create mode 100644 bindings/python/Jamfile create mode 100755 bindings/python/client.py create mode 100644 bindings/python/dummy_data.py 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/info_hash.cpp 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/sha256_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 100644 bindings/python/test.py create mode 100644 clang_tidy.jam 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 100644 deps/asio-gnutls/Jamfile create mode 100644 deps/asio-gnutls/LICENSE_1_0.txt create mode 100644 deps/asio-gnutls/README.md create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/context.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/context_base.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/error.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/host_name_verification.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/rfc2818_verification.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/stream.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/stream_base.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/verify_context.hpp create mode 100644 deps/asio-gnutls/test/gnutls/Jamfile.v2 create mode 100644 deps/asio-gnutls/test/gnutls/context.cpp create mode 100644 deps/asio-gnutls/test/gnutls/context_base.cpp create mode 100644 deps/asio-gnutls/test/gnutls/error.cpp create mode 100644 deps/asio-gnutls/test/gnutls/host_name_verification.cpp create mode 100644 deps/asio-gnutls/test/gnutls/rfc2818_verification.cpp create mode 100644 deps/asio-gnutls/test/gnutls/stream.cpp create mode 100644 deps/asio-gnutls/test/gnutls/stream_base.cpp create mode 100644 deps/asio-gnutls/test/unit_test.hpp create mode 100644 deps/try_signal/CMakeLists.txt create mode 100644 deps/try_signal/Jamfile create mode 100644 deps/try_signal/LICENSE create mode 100644 deps/try_signal/README.rst create mode 100644 deps/try_signal/appveyor.yml create mode 100644 deps/try_signal/example.cpp create mode 100644 deps/try_signal/project-root.jam create mode 100644 deps/try_signal/signal_error_code.cpp create mode 100644 deps/try_signal/signal_error_code.hpp create mode 100644 deps/try_signal/test.cpp create mode 100644 deps/try_signal/try_signal.cpp create mode 100644 deps/try_signal/try_signal.hpp create mode 100644 deps/try_signal/try_signal_mingw.hpp create mode 100644 deps/try_signal/try_signal_msvc.hpp create mode 100644 deps/try_signal/try_signal_posix.hpp create mode 100644 docs/2020 Q4 Mozilla Libtorrent Report Public Report.pdf create mode 100755 docs/build_version.sh create mode 100644 docs/building.rst create mode 100644 docs/client_test.rst create mode 100644 docs/contributing.rst create mode 100644 docs/dht_extensions.rst create mode 100644 docs/dht_rss.rst create mode 100644 docs/dht_sec.rst create mode 100644 docs/dht_store.rst create mode 100644 docs/examples.rst create mode 100644 docs/extension_protocol.rst create mode 100644 docs/features.rst create mode 100644 docs/filter-rst.py create mode 100644 docs/fuzzing.rst create mode 100644 docs/gen_reference_doc.py create mode 100755 docs/gen_settings_doc.py create mode 100755 docs/gen_stats_doc.py create mode 100755 docs/gen_todo.py create mode 100644 docs/hacking.rst create mode 100644 docs/header.rst create mode 100644 docs/hunspell/en_US.aff create mode 100644 docs/hunspell/en_US.dic create mode 100644 docs/hunspell/libtorrent.dic create mode 100644 docs/img/bitcoin.png create mode 100644 docs/img/complete_bit_prefixes.png create mode 100644 docs/img/cwnd.png create mode 100644 docs/img/delays.png create mode 100644 docs/img/hacking.diagram create mode 100644 docs/img/hash_distribution.png create mode 100644 docs/img/ip_id_v4.png create mode 100644 docs/img/ip_id_v6.png create mode 100644 docs/img/logo-bw.svg create mode 100644 docs/img/logo-color-text-vertical.svg create mode 100644 docs/img/logo-color-text.png create mode 100644 docs/img/logo-color-text.svg create mode 100644 docs/img/logo-color.svg create mode 100644 docs/img/logo.svg create mode 100644 docs/img/our_delay_base.png create mode 100644 docs/img/pp-acceptance-medium.png create mode 100644 docs/img/read_disk_buffers.diagram create mode 100644 docs/img/screenshot.png create mode 100644 docs/img/storage.diagram create mode 100644 docs/img/troubleshooting.dot create mode 100644 docs/img/utp_stack.diagram create mode 100644 docs/img/write_disk_buffers.diagram create mode 100644 docs/include/libtorrent/add_torrent_params.hpp create mode 100644 docs/include/libtorrent/address.hpp create mode 100644 docs/include/libtorrent/alert.hpp create mode 100644 docs/include/libtorrent/alert_types.hpp create mode 100644 docs/include/libtorrent/announce_entry.hpp create mode 100644 docs/include/libtorrent/assert.hpp create mode 100644 docs/include/libtorrent/bdecode.hpp create mode 100644 docs/include/libtorrent/bencode.hpp create mode 100644 docs/include/libtorrent/bitfield.hpp create mode 100644 docs/include/libtorrent/bloom_filter.hpp create mode 100644 docs/include/libtorrent/bt_peer_connection.hpp create mode 100644 docs/include/libtorrent/choker.hpp create mode 100644 docs/include/libtorrent/client_data.hpp create mode 100644 docs/include/libtorrent/close_reason.hpp create mode 100644 docs/include/libtorrent/config.hpp create mode 100644 docs/include/libtorrent/copy_ptr.hpp create mode 100644 docs/include/libtorrent/crc32c.hpp create mode 100644 docs/include/libtorrent/create_torrent.hpp create mode 100644 docs/include/libtorrent/deadline_timer.hpp create mode 100644 docs/include/libtorrent/debug.hpp create mode 100644 docs/include/libtorrent/disabled_disk_io.hpp create mode 100644 docs/include/libtorrent/disk_buffer_holder.hpp create mode 100644 docs/include/libtorrent/disk_interface.hpp create mode 100644 docs/include/libtorrent/disk_observer.hpp create mode 100644 docs/include/libtorrent/download_priority.hpp create mode 100644 docs/include/libtorrent/entry.hpp create mode 100644 docs/include/libtorrent/enum_net.hpp create mode 100644 docs/include/libtorrent/error.hpp create mode 100644 docs/include/libtorrent/error_code.hpp create mode 100644 docs/include/libtorrent/extensions.hpp create mode 100644 docs/include/libtorrent/file.hpp create mode 100644 docs/include/libtorrent/file_storage.hpp create mode 100644 docs/include/libtorrent/fingerprint.hpp create mode 100644 docs/include/libtorrent/flags.hpp create mode 100644 docs/include/libtorrent/fwd.hpp create mode 100644 docs/include/libtorrent/gzip.hpp create mode 100644 docs/include/libtorrent/hash_picker.hpp create mode 100644 docs/include/libtorrent/hasher.hpp create mode 100644 docs/include/libtorrent/hex.hpp create mode 100644 docs/include/libtorrent/http_connection.hpp create mode 100644 docs/include/libtorrent/http_parser.hpp create mode 100644 docs/include/libtorrent/http_seed_connection.hpp create mode 100644 docs/include/libtorrent/http_stream.hpp create mode 100644 docs/include/libtorrent/http_tracker_connection.hpp create mode 100644 docs/include/libtorrent/i2p_stream.hpp create mode 100644 docs/include/libtorrent/identify_client.hpp create mode 100644 docs/include/libtorrent/index_range.hpp create mode 100644 docs/include/libtorrent/info_hash.hpp create mode 100644 docs/include/libtorrent/io.hpp create mode 100644 docs/include/libtorrent/io_context.hpp create mode 100644 docs/include/libtorrent/io_service.hpp create mode 100644 docs/include/libtorrent/ip_filter.hpp create mode 100644 docs/include/libtorrent/ip_voter.hpp create mode 100644 docs/include/libtorrent/libtorrent.hpp create mode 100644 docs/include/libtorrent/link.hpp create mode 100644 docs/include/libtorrent/lsd.hpp create mode 100644 docs/include/libtorrent/magnet_uri.hpp create mode 100644 docs/include/libtorrent/mmap_disk_io.hpp create mode 100644 docs/include/libtorrent/mmap_storage.hpp create mode 100644 docs/include/libtorrent/natpmp.hpp create mode 100644 docs/include/libtorrent/netlink.hpp create mode 100644 docs/include/libtorrent/operations.hpp create mode 100644 docs/include/libtorrent/optional.hpp create mode 100644 docs/include/libtorrent/parse_url.hpp create mode 100644 docs/include/libtorrent/part_file.hpp create mode 100644 docs/include/libtorrent/pe_crypto.hpp create mode 100644 docs/include/libtorrent/peer.hpp create mode 100644 docs/include/libtorrent/peer_class.hpp create mode 100644 docs/include/libtorrent/peer_class_set.hpp create mode 100644 docs/include/libtorrent/peer_class_type_filter.hpp create mode 100644 docs/include/libtorrent/peer_connection.hpp create mode 100644 docs/include/libtorrent/peer_connection_handle.hpp create mode 100644 docs/include/libtorrent/peer_connection_interface.hpp create mode 100644 docs/include/libtorrent/peer_id.hpp create mode 100644 docs/include/libtorrent/peer_info.hpp create mode 100644 docs/include/libtorrent/peer_list.hpp create mode 100644 docs/include/libtorrent/peer_request.hpp create mode 100644 docs/include/libtorrent/performance_counters.hpp create mode 100644 docs/include/libtorrent/pex_flags.hpp create mode 100644 docs/include/libtorrent/piece_block.hpp create mode 100644 docs/include/libtorrent/piece_block_progress.hpp create mode 100644 docs/include/libtorrent/piece_picker.hpp create mode 100644 docs/include/libtorrent/platform_util.hpp create mode 100644 docs/include/libtorrent/portmap.hpp create mode 100644 docs/include/libtorrent/posix_disk_io.hpp create mode 100644 docs/include/libtorrent/proxy_base.hpp create mode 100644 docs/include/libtorrent/puff.hpp create mode 100644 docs/include/libtorrent/random.hpp create mode 100644 docs/include/libtorrent/read_resume_data.hpp create mode 100644 docs/include/libtorrent/request_blocks.hpp create mode 100644 docs/include/libtorrent/resolve_links.hpp create mode 100644 docs/include/libtorrent/session.hpp create mode 100644 docs/include/libtorrent/session_handle.hpp create mode 100644 docs/include/libtorrent/session_params.hpp create mode 100644 docs/include/libtorrent/session_settings.hpp create mode 100644 docs/include/libtorrent/session_stats.hpp create mode 100644 docs/include/libtorrent/session_status.hpp create mode 100644 docs/include/libtorrent/session_types.hpp create mode 100644 docs/include/libtorrent/settings_pack.hpp create mode 100644 docs/include/libtorrent/sha1.hpp create mode 100644 docs/include/libtorrent/sha1_hash.hpp create mode 100644 docs/include/libtorrent/sha256.hpp create mode 100644 docs/include/libtorrent/sliding_average.hpp create mode 100644 docs/include/libtorrent/socket.hpp create mode 100644 docs/include/libtorrent/socket_io.hpp create mode 100644 docs/include/libtorrent/socket_type.hpp create mode 100644 docs/include/libtorrent/socks5_stream.hpp create mode 100644 docs/include/libtorrent/span.hpp create mode 100644 docs/include/libtorrent/ssl.hpp create mode 100644 docs/include/libtorrent/ssl_stream.hpp create mode 100644 docs/include/libtorrent/stack_allocator.hpp create mode 100644 docs/include/libtorrent/stat.hpp create mode 100644 docs/include/libtorrent/stat_cache.hpp create mode 100644 docs/include/libtorrent/storage.hpp create mode 100644 docs/include/libtorrent/storage_defs.hpp create mode 100644 docs/include/libtorrent/string_util.hpp create mode 100644 docs/include/libtorrent/string_view.hpp create mode 100644 docs/include/libtorrent/tailqueue.hpp create mode 100644 docs/include/libtorrent/time.hpp create mode 100644 docs/include/libtorrent/torrent.hpp create mode 100644 docs/include/libtorrent/torrent_flags.hpp create mode 100644 docs/include/libtorrent/torrent_handle.hpp create mode 100644 docs/include/libtorrent/torrent_info.hpp create mode 100644 docs/include/libtorrent/torrent_peer.hpp create mode 100644 docs/include/libtorrent/torrent_peer_allocator.hpp create mode 100644 docs/include/libtorrent/torrent_status.hpp create mode 100644 docs/include/libtorrent/tracker_manager.hpp create mode 100644 docs/include/libtorrent/truncate.hpp create mode 100644 docs/include/libtorrent/udp_socket.hpp create mode 100644 docs/include/libtorrent/udp_tracker_connection.hpp create mode 100644 docs/include/libtorrent/union_endpoint.hpp create mode 100644 docs/include/libtorrent/units.hpp create mode 100644 docs/include/libtorrent/upnp.hpp create mode 100644 docs/include/libtorrent/utf8.hpp create mode 100644 docs/include/libtorrent/vector_utils.hpp create mode 100644 docs/include/libtorrent/version.hpp create mode 100644 docs/include/libtorrent/web_connection_base.hpp create mode 100644 docs/include/libtorrent/web_peer_connection.hpp create mode 100644 docs/include/libtorrent/write_resume_data.hpp create mode 100644 docs/include/libtorrent/xml_parse.hpp create mode 100644 docs/index.rst create mode 100644 docs/makefile create mode 100644 docs/manual.rst create mode 100644 docs/plain_text_out.txt create mode 100644 docs/projects.rst create mode 100644 docs/python_binding.rst create mode 100644 docs/security-audit.rst create mode 100644 docs/settings-ref.rst create mode 100644 docs/single-page-ref.rst create mode 100644 docs/streaming.rst create mode 100644 docs/style.css create mode 100644 docs/stylesheet create mode 100644 docs/template.txt create mode 100644 docs/template2.txt create mode 100644 docs/troubleshooting.rst create mode 100644 docs/troubleshooting_thumb.png create mode 100644 docs/tuning.rst create mode 100644 docs/tutorial.rst create mode 100644 docs/udp_tracker_protocol.rst create mode 100644 docs/upgrade_to_1.2.rst create mode 100644 docs/upgrade_to_2.0.rst create mode 100644 docs/utp.rst create mode 100644 examples/CMakeLists.txt create mode 100644 examples/Jamfile create mode 100644 examples/Makefile.am create mode 100644 examples/bt-get.cpp create mode 100644 examples/bt-get2.cpp create mode 100644 examples/bt-get3.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_bdecode.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 100755 examples/run_benchmarks.py 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/torrent2magnet.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 fuzzers/Jamfile create mode 100644 fuzzers/LICENSE create mode 100644 fuzzers/README.rst create mode 100644 fuzzers/main.cpp create mode 100755 fuzzers/minimize.sh create mode 100755 fuzzers/run.sh create mode 100644 fuzzers/src/add_torrent.cpp create mode 100644 fuzzers/src/base32decode.cpp create mode 100644 fuzzers/src/base32encode.cpp create mode 100644 fuzzers/src/base64encode.cpp create mode 100644 fuzzers/src/bdecode_node.cpp create mode 100644 fuzzers/src/convert_from_native.cpp create mode 100644 fuzzers/src/convert_to_native.cpp create mode 100644 fuzzers/src/dht_node.cpp create mode 100644 fuzzers/src/escape_path.cpp create mode 100644 fuzzers/src/escape_string.cpp create mode 100644 fuzzers/src/file_storage_add_file.cpp create mode 100644 fuzzers/src/gzip.cpp create mode 100644 fuzzers/src/http_parser.cpp create mode 100644 fuzzers/src/http_tracker.cpp create mode 100644 fuzzers/src/idna.cpp create mode 100644 fuzzers/src/parse_int.cpp create mode 100644 fuzzers/src/parse_magnet_uri.cpp create mode 100644 fuzzers/src/parse_url.cpp create mode 100644 fuzzers/src/peer_conn.cpp create mode 100644 fuzzers/src/read_bits.hpp create mode 100644 fuzzers/src/resume_data.cpp create mode 100644 fuzzers/src/sanitize_path.cpp create mode 100644 fuzzers/src/session_params.cpp create mode 100644 fuzzers/src/torrent_info.cpp create mode 100644 fuzzers/src/upnp.cpp create mode 100644 fuzzers/src/utf8_codepoint.cpp create mode 100644 fuzzers/src/utp.cpp create mode 100644 fuzzers/src/verify_encoding.cpp create mode 100644 fuzzers/tools/generate_initial_corpus.py create mode 100644 fuzzers/tools/unify_corpus_names.py 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_types.hpp create mode 100644 include/libtorrent/announce_entry.hpp create mode 100644 include/libtorrent/assert.hpp create mode 100644 include/libtorrent/aux_/alert_manager.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_/announce_entry.hpp create mode 100644 include/libtorrent/aux_/apply_pad_files.hpp create mode 100644 include/libtorrent/aux_/array.hpp create mode 100644 include/libtorrent/aux_/bandwidth_limit.hpp create mode 100644 include/libtorrent/aux_/bandwidth_manager.hpp create mode 100644 include/libtorrent/aux_/bandwidth_queue_entry.hpp create mode 100644 include/libtorrent/aux_/bandwidth_socket.hpp create mode 100644 include/libtorrent/aux_/bind_to_device.hpp create mode 100644 include/libtorrent/aux_/buffer.hpp create mode 100644 include/libtorrent/aux_/byteswap.hpp create mode 100644 include/libtorrent/aux_/chained_buffer.hpp create mode 100644 include/libtorrent/aux_/container_wrapper.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_/directory.hpp create mode 100644 include/libtorrent/aux_/disable_deprecation_warnings_push.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_buffer_pool.hpp create mode 100644 include/libtorrent/aux_/disk_io_thread_pool.hpp create mode 100644 include/libtorrent/aux_/disk_job_fence.hpp create mode 100644 include/libtorrent/aux_/disk_job_pool.hpp create mode 100644 include/libtorrent/aux_/ed25519.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_pointer.hpp create mode 100644 include/libtorrent/aux_/file_progress.hpp create mode 100644 include/libtorrent/aux_/file_view_pool.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_/hasher512.hpp create mode 100644 include/libtorrent/aux_/heterogeneous_queue.hpp create mode 100644 include/libtorrent/aux_/instantiate_connection.hpp create mode 100644 include/libtorrent/aux_/invariant_check.hpp create mode 100644 include/libtorrent/aux_/io.hpp create mode 100644 include/libtorrent/aux_/ip_helpers.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_/merkle_tree.hpp create mode 100644 include/libtorrent/aux_/mmap.hpp create mode 100644 include/libtorrent/aux_/mmap_disk_job.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_/open_mode.hpp create mode 100644 include/libtorrent/aux_/packet_buffer.hpp create mode 100644 include/libtorrent/aux_/packet_pool.hpp create mode 100644 include/libtorrent/aux_/path.hpp create mode 100644 include/libtorrent/aux_/polymorphic_socket.hpp create mode 100644 include/libtorrent/aux_/pool.hpp create mode 100644 include/libtorrent/aux_/portmap.hpp create mode 100644 include/libtorrent/aux_/posix_part_file.hpp create mode 100644 include/libtorrent/aux_/posix_storage.hpp create mode 100644 include/libtorrent/aux_/proxy_settings.hpp create mode 100644 include/libtorrent/aux_/range.hpp create mode 100644 include/libtorrent/aux_/receive_buffer.hpp create mode 100644 include/libtorrent/aux_/resolver.hpp create mode 100644 include/libtorrent/aux_/resolver_interface.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_/set_traffic_class.hpp create mode 100644 include/libtorrent/aux_/sha512.hpp create mode 100644 include/libtorrent/aux_/socket_type.hpp create mode 100644 include/libtorrent/aux_/storage_free_list.hpp create mode 100644 include/libtorrent/aux_/storage_utils.hpp create mode 100644 include/libtorrent/aux_/store_buffer.hpp create mode 100644 include/libtorrent/aux_/string_ptr.hpp create mode 100644 include/libtorrent/aux_/strview_less.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_/timestamp_history.hpp create mode 100644 include/libtorrent/aux_/torrent_impl.hpp create mode 100644 include/libtorrent/aux_/torrent_list.hpp create mode 100644 include/libtorrent/aux_/unique_ptr.hpp create mode 100644 include/libtorrent/aux_/utp_socket_manager.hpp create mode 100644 include/libtorrent/aux_/utp_stream.hpp create mode 100644 include/libtorrent/aux_/vector.hpp create mode 100644 include/libtorrent/aux_/win_cng.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/bdecode.hpp create mode 100644 include/libtorrent/bencode.hpp create mode 100644 include/libtorrent/bitfield.hpp create mode 100644 include/libtorrent/bloom_filter.hpp create mode 100644 include/libtorrent/bt_peer_connection.hpp create mode 100644 include/libtorrent/choker.hpp create mode 100644 include/libtorrent/client_data.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/disabled_disk_io.hpp create mode 100644 include/libtorrent/disk_buffer_holder.hpp create mode 100644 include/libtorrent/disk_interface.hpp create mode 100644 include/libtorrent/disk_observer.hpp create mode 100644 include/libtorrent/download_priority.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_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/hash_picker.hpp create mode 100644 include/libtorrent/hasher.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/info_hash.hpp create mode 100644 include/libtorrent/io.hpp create mode 100644 include/libtorrent/io_context.hpp create mode 100644 include/libtorrent/io_service.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/libtorrent.hpp create mode 100644 include/libtorrent/link.hpp create mode 100644 include/libtorrent/lsd.hpp create mode 100644 include/libtorrent/magnet_uri.hpp create mode 100644 include/libtorrent/mmap_disk_io.hpp create mode 100644 include/libtorrent/mmap_storage.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/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/posix_disk_io.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/request_blocks.hpp create mode 100644 include/libtorrent/resolve_links.hpp create mode 100644 include/libtorrent/session.hpp create mode 100644 include/libtorrent/session_handle.hpp create mode 100644 include/libtorrent/session_params.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/sha256.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/socket_type.hpp create mode 100644 include/libtorrent/socks5_stream.hpp create mode 100644 include/libtorrent/span.hpp create mode 100644 include/libtorrent/ssl.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/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/truncate.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/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 project-config.jam create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 simulation/Jamfile create mode 100644 simulation/create_torrent.cpp create mode 100644 simulation/create_torrent.hpp create mode 100644 simulation/disk_io.cpp create mode 100644 simulation/disk_io.hpp create mode 100644 simulation/fake_peer.hpp create mode 100644 simulation/make_proxy_settings.hpp create mode 100644 simulation/setup_dht.cpp create mode 100644 simulation/setup_dht.hpp create mode 100644 simulation/setup_swarm.cpp create mode 100644 simulation/setup_swarm.hpp create mode 100644 simulation/test_auto_manage.cpp create mode 100644 simulation/test_checking.cpp create mode 100644 simulation/test_dht.cpp create mode 100644 simulation/test_dht_bootstrap.cpp create mode 100644 simulation/test_dht_rate_limit.cpp create mode 100644 simulation/test_dht_storage.cpp create mode 100644 simulation/test_error_handling.cpp create mode 100644 simulation/test_fast_extensions.cpp create mode 100644 simulation/test_file_pool.cpp create mode 100644 simulation/test_http_connection.cpp create mode 100644 simulation/test_ip_filter.cpp create mode 100644 simulation/test_metadata_extension.cpp create mode 100644 simulation/test_optimistic_unchoke.cpp create mode 100644 simulation/test_pause.cpp create mode 100644 simulation/test_pe_crypto.cpp create mode 100644 simulation/test_peer_connection.cpp create mode 100644 simulation/test_save_resume.cpp create mode 100644 simulation/test_session.cpp create mode 100644 simulation/test_socks5.cpp create mode 100644 simulation/test_super_seeding.cpp create mode 100644 simulation/test_swarm.cpp create mode 100644 simulation/test_thread_pool.cpp create mode 100644 simulation/test_timeout.cpp create mode 100644 simulation/test_torrent_status.cpp create mode 100644 simulation/test_tracker.cpp create mode 100644 simulation/test_transfer.cpp create mode 100644 simulation/test_transfer_matrix.cpp create mode 100644 simulation/test_utp.cpp create mode 100644 simulation/test_web_seed.cpp create mode 100644 simulation/transfer_sim.cpp create mode 100644 simulation/transfer_sim.hpp create mode 100644 simulation/utils.cpp create mode 100644 simulation/utils.hpp 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/bloom_filter.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/copy_file.cpp create mode 100644 src/cpuid.cpp create mode 100644 src/crc32c.cpp create mode 100644 src/create_torrent.cpp create mode 100644 src/directory.cpp create mode 100644 src/disabled_disk_io.cpp create mode 100644 src/disk_buffer_holder.cpp create mode 100644 src/disk_buffer_pool.cpp create mode 100644 src/disk_interface.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/ed25519/LICENSE create mode 100644 src/ed25519/add_scalar.cpp create mode 100644 src/ed25519/fe.cpp create mode 100644 src/ed25519/fe.h create mode 100644 src/ed25519/fixedint.h create mode 100644 src/ed25519/ge.cpp create mode 100644 src/ed25519/ge.h create mode 100644 src/ed25519/hasher512.cpp create mode 100644 src/ed25519/key_exchange.cpp create mode 100644 src/ed25519/keypair.cpp create mode 100644 src/ed25519/precomp_data.h create mode 100644 src/ed25519/sc.cpp create mode 100644 src/ed25519/sc.h create mode 100644 src/ed25519/sha512.cpp create mode 100644 src/ed25519/sign.cpp create mode 100644 src/ed25519/verify.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_progress.cpp create mode 100644 src/file_storage.cpp create mode 100644 src/file_view_pool.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/hash_picker.cpp create mode 100644 src/hasher.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_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_helpers.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/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/merkle_tree.cpp create mode 100644 src/mmap.cpp create mode 100644 src/mmap_disk_io.cpp create mode 100644 src/mmap_disk_job.cpp create mode 100644 src/mmap_storage.cpp create mode 100644 src/natpmp.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/posix_disk_io.cpp create mode 100644 src/posix_part_file.cpp create mode 100644 src/posix_storage.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_params.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/sha256.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/ssl.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_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/truncate.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/bittorrent_peer.cpp create mode 100644 test/bittorrent_peer.hpp create mode 100644 test/broadcast_socket.cpp create mode 100644 test/broadcast_socket.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_add_torrent.cpp 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_apply_pad.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_bloom_filter.cpp create mode 100644 test/test_buffer.cpp create mode 100644 test/test_checking.cpp create mode 100644 test/test_copy_file.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_hash_picker.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_info_hash.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_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_merkle_tree.cpp create mode 100644 test/test_mmap.cpp create mode 100644 test/test_natpmp.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_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_similar_torrent.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_store_buffer.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_torrent_list.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-files-1.torrent create mode 100644 test/test_torrents/empty-files-2.torrent create mode 100644 test/test_torrents/empty-files-3.torrent create mode 100644 test/test_torrents/empty-files-4.torrent create mode 100644 test/test_torrents/empty-files-5.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_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_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/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/v2.torrent create mode 100644 test/test_torrents/v2_bad_file_alignment.torrent create mode 100644 test/test_torrents/v2_deep_recursion.torrent create mode 100644 test/test_torrents/v2_empty_file.torrent create mode 100644 test/test_torrents/v2_hybrid.torrent create mode 100644 test/test_torrents/v2_incomplete_piece_layer.torrent create mode 100644 test/test_torrents/v2_invalid_file.torrent create mode 100644 test/test_torrents/v2_invalid_filename.torrent create mode 100644 test/test_torrents/v2_invalid_pad_file.torrent create mode 100644 test/test_torrents/v2_invalid_piece_layer.torrent create mode 100644 test/test_torrents/v2_invalid_piece_layer_size.torrent create mode 100755 test/test_torrents/v2_invalid_root_hash.torrent create mode 100644 test/test_torrents/v2_large_file.torrent create mode 100644 test/test_torrents/v2_large_offset.torrent create mode 100644 test/test_torrents/v2_mismatching_metadata.torrent create mode 100644 test/test_torrents/v2_missing_file_root_invalid_symlink.torrent create mode 100644 test/test_torrents/v2_multipiece_file.torrent create mode 100644 test/test_torrents/v2_multiple_files.torrent create mode 100644 test/test_torrents/v2_no_piece_layers.torrent create mode 100644 test/test_torrents/v2_no_power2_piece.torrent create mode 100644 test/test_torrents/v2_non_multiple_piece_layer.torrent create mode 100644 test/test_torrents/v2_only.torrent create mode 100644 test/test_torrents/v2_overlong_integer.torrent create mode 100644 test/test_torrents/v2_piece_layer_invalid_file_hash.torrent create mode 100644 test/test_torrents/v2_piece_size.torrent create mode 100644 test/test_torrents/v2_symlinks.torrent create mode 100644 test/test_torrents/v2_unordered_files.torrent create mode 100644 test/test_torrents/v2_zero_root.torrent create mode 100644 test/test_torrents/v2_zero_root_small.torrent create mode 100644 test/test_torrents/whitespace_url.torrent create mode 100644 test/test_torrents/zero.torrent create mode 100644 test/test_torrents/zero2.torrent create mode 100644 test/test_tracker.cpp create mode 100644 test/test_transfer.cpp create mode 100644 test/test_truncate.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/valgrind_suppressions.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 100755 tools/benchmark_checking.py create mode 100755 tools/cibuildwheel/manylinux/build-openssl.sh create mode 100755 tools/cibuildwheel/manylinux/build_utils.sh create mode 100755 tools/cibuildwheel/manylinux/openssl-version.sh create mode 100755 tools/cibuildwheel/setup_boost.sh create mode 100755 tools/cibuildwheel/setup_ccache_on_manylinux.sh create mode 100755 tools/cibuildwheel/setup_openssl.sh create mode 100755 tools/clean.py create mode 100644 tools/copyright.py create mode 100755 tools/dht_flood.py create mode 100644 tools/dht_put.cpp create mode 100644 tools/dht_sample.cpp create mode 100644 tools/disk_io_stress_test.cpp create mode 100644 tools/gen_convenience_header.py create mode 100644 tools/gen_fwd.py create mode 100644 tools/libtorrent_lldb.py 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_lookup_log.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 100755 tools/run_benchmark.py create mode 100755 tools/run_tests.sh create mode 100644 tools/sanitizer-blacklist.txt create mode 100644 tools/session_log_alerts.cpp create mode 100755 tools/set_version.py create mode 100755 tools/test_coverage.sh create mode 100755 tools/update_copyright.py create mode 100644 tools/vmstat.py diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 0000000..5f6d4a0 --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,21 @@ +freebsd_instance: + image_family: freebsd-12-1 + +task: + install_script: | + IGNORE_OSVERSION=yes pkg update + pkg install -U -y git boost_build boost-libs unzip wget openssl cmake ninja + echo "using clang ;" > ~/user-config.jam + submodules_script: | + git submodule update --init --recursive + build_cmake_script: | + cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-Werror" -DCMAKE_CXX_STANDARD=14 -Dbuild_tests=ON -Dbuild_examples=ON -Dbuild_tools=ON -Dpython-bindings=OFF -G Ninja . + cmake --build . --parallel 2 + ./test/test_primitives + tests_script: | + cd test + b2 -l250 warnings-as-errors=on warnings=all crypto=openssl deterministic-tests include=/usr/local/include library-path=/usr/local/lib + enum_if_script: | + cd test + b2 -l250 warnings-as-errors=on warnings=all crypto=openssl stage_enum_if stage_dependencies include=/usr/local/include library-path=/usr/local/lib + LD_LIBRARY_PATH=./dependencies ./enum_if diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst new file mode 100644 index 0000000..d079c55 --- /dev/null +++ b/.github/CONTRIBUTING.rst @@ -0,0 +1,51 @@ +For general contribution guidelines, see `www.contribution-guide.org`__. + +.. __: http://www.contribution-guide.org/ + +For general code style, see `c++ core guidelines`__. + +.. __: http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines + +bug reporting checklist +....................... + +Please keep in mind that there are 2 different ways of building +libtorrent (boost-build and cmake). There are also a number of build +time configuration options (``TORRENT_*`` macros). If it may be relevant, please +include how libtorrent was built in your bug report. + +boost-build is the preferred build system and the root ``Makefile`` is the + package tool. If there's a problem with a build script, please +consider posting a pull request with a fix. + +Please be explicit about the behavior you see, ideally boil it down to its +essentials and provide example code, calling into libtorrent, reproducing the +problem. + +For bittorrent protocol level issues, please include session, torrent and peer +level logs. Logs are enabled in the ``alert_mask``. + +For tracker issues, please include a wireshark dump of the tracker announce +and response. It may be useful to also include session and torrent logs. + +pull request checklist +...................... + +When creating a pull request, please consider the following checklist: + +* make sure both CI is green ("Checks" on github). Note that on gcc and + clang warnings are treated as errors. Some tests may be flapping, if so, + please issue a rebuild of the specific build configuration. (I'm working on + making all tests deterministic) +* please make sure to add appropriate comments. For client-facing changes, + update the documentation comments in the public header (accepts restructured + text) +* If adding a client-facing feature, please add brief entry to ``ChangeLog`` +* Add a unit test (``tests``) or a regression test (``simulations``) to confirm + the new behavior or feature. Don't forget negative tests (failure cases) and + please pay as much care to tests as you would production code. +* if your patch adds a new file, please make sure it's added to + the ``Jamfile``, ``Makefile`` and ``CMakeList.txt``. +* Changes that alter the ABI (size, order or number of fields on public classes) + should target the ``master`` branch. The stable branches (``RC_*_*``) may not + change ABI. diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..87559ed --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: arvidn +custom: https://paypal.me/arvidno diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..6c014d0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,11 @@ +**Please provide the following information** + +libtorrent version (or branch): + +platform/architecture: + +compiler and compiler version: + +please describe what symptom you see, what you would expect to see instead and +how to reproduce it. + diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..20b848f --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,4 @@ +Report vulnerabilities to one of: + +* ``arvidn`` on keybase +* arvid@libtorrent.org diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000..4e1d491 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,25 @@ +codecov: + require_ci_to_pass: yes + +coverage: + precision: 2 + round: nearest + range: "40...100" + +ignore: + - test + - simulation + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "diff,flags,tree" + behavior: default + require_changes: no + after_n_builds: 2 diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..d51e717 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,18 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 90 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 20 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security +# Label to use when marking an issue as stale +staleLabel: stale +exemptMilestones: true +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000..74b57d3 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,69 @@ +name: Android + +on: + push: + branches: [ RC_2_0 master ] + pull_request: + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }} + cancel-in-progress: true + +env: + NDK_VERSION: "r21d" + OPENSSL_VERSION: "1.1.1i" + OPENSSL_OPTS: "no-deprecated no-shared no-makedepend -fvisibility=hidden -O3" + +jobs: + + android_arm32_build: + name: Build Android Arm 32bits + runs-on: ubuntu-20.04 + + steps: + - name: checkout + uses: actions/checkout@v2.3.3 + with: + submodules: true + + - name: install boost + run: | + git clone --depth=1 --branch=boost-1.75.0 https://github.com/boostorg/boost.git + cd boost + git submodule update --init --depth=1 + ./bootstrap.sh + ./b2 headers + cd .. + + - name: install ndk + run: | + wget -nv -O android-ndk.zip https://dl.google.com/android/repository/android-ndk-${NDK_VERSION}-linux-x86_64.zip + unzip -qq android-ndk.zip + export NDK=${PWD}/android-ndk-${NDK_VERSION} + ${NDK}/build/tools/make_standalone_toolchain.py --arch arm --api 19 --stl libc++ --install-dir android-toolchain + + - name: install openssl + run: | + export ANDROID_TOOLCHAIN=${PWD}/android-toolchain + export PATH=${ANDROID_TOOLCHAIN}/arm-linux-androideabi/bin:${PATH} + export CC=${ANDROID_TOOLCHAIN}/bin/arm-linux-androideabi-clang + wget -nv -O openssl.tar.gz https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz + tar xzf openssl.tar.gz + cd openssl-${OPENSSL_VERSION} + ./Configure linux-armv4 ${OPENSSL_OPTS} -march=armv7-a -mfpu=neon -fPIC --prefix=${PWD}/../openssl + make &> /dev/null + make install_sw &> /dev/null + cd .. + + - name: build library + run: | + export ANDROID_TOOLCHAIN=${PWD}/android-toolchain + export PATH=${ANDROID_TOOLCHAIN}/arm-linux-androideabi/bin:${PATH} + export BOOST_ROOT=${PWD}/boost + export OPENSSL_ROOT=${PWD}/openssl + echo "boost-build ${BOOST_ROOT}/tools/build/src ;" > boost-build.jam + echo "using clang-linux : arm : ${ANDROID_TOOLCHAIN}/bin/arm-linux-androideabi-clang++ + -fPIC + -march=armv7-a + -mfpu=neon ;" >>~/user-config.jam; + ${BOOST_ROOT}/b2 warnings-as-errors=on cxxstd=14 toolset=clang-linux-arm target-os=android link=static crypto=openssl openssl-include=${OPENSSL_ROOT}/include openssl-lib=${OPENSSL_ROOT}/lib diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml new file mode 100644 index 0000000..1484899 --- /dev/null +++ b/.github/workflows/cibuildwheel.yml @@ -0,0 +1,147 @@ +name: cibuildwheel + +# Note: We use a dynamic matrix to build different sets of wheels under +# different conditions. On workflow_dispatch, we build the full suite of +# wheels. This takes hours, so on pull_request, we just build a representative +# sample. + +# The full list of cibuildwheel's build targets can be found here: +# https://github.com/pypa/cibuildwheel/blob/v2.2.0a1/cibuildwheel/resources/build-platforms.toml + +# Notes on build targets we (don't) support: +# - pypy: libtorrent doesn't build with pypy as of writing +# - macos_arm64: can be cross-compiled from x86_64, but not run, so can't be +# tested. Build output indicates it isn't building correctly +# - macos_universal2: b2 / setup.py doesn't have a straightforward way to build +# this as of writing +# - abi3: Not supported by boost-python (or pybind11) or cibuildwheel as of +# writing + +on: + workflow_dispatch: + inputs: + publish: + description: Write 'PUBLISH' to publish to pypi. BEWARE! ARTIFACTS ARE IMMUTABLE AND CANNOT BE REPLACED ONCE PUBLISHED! + publish_test: + description: Write 'PUBLISH_TEST' to publish to test-pypi. BEWARE! ARTIFACTS ARE IMMUTABLE AND CANNOT BE REPLACED ONCE PUBLISHED! + + pull_request: + paths: + - .github/workflows/cibuildwheel.yml + - tools/cibuildwheel/** + - pyproject.toml + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }} + cancel-in-progress: true + +jobs: + + configure_matrix: + runs-on: ubuntu-latest + env: + # github actions syntax doesn't allow us to have yaml structures as + # an input to a job. These environment variables are literal json strings + MATRIX_PULL_REQUEST: | + { + "include": [ + {"os": "ubuntu-20.04", "CIBW_BUILD": "cp37-manylinux_*", "CIBW_ARCHS": "x86_64"}, + {"os": "ubuntu-20.04", "CIBW_BUILD": "cp37-musllinux_*", "CIBW_ARCHS": "x86_64"}, + {"os": "macos-10.15", "CIBW_BUILD": "cp37-*", "CIBW_ARCHS": "x86_64"}, + {"os": "windows-2019", "CIBW_BUILD": "cp37-*", "CIBW_ARCHS": "AMD64"} + ] + } + MATRIX_WORKFLOW_DISPATCH: | + { + "include": [ + {"os": "ubuntu-20.04", "CIBW_BUILD": "cp*-manylinux_*", "CIBW_ARCHS": "x86_64"}, + {"os": "ubuntu-20.04", "CIBW_BUILD": "cp*-manylinux_*", "CIBW_ARCHS": "aarch64"}, + {"os": "ubuntu-20.04", "CIBW_BUILD": "cp*-musllinux_*", "CIBW_ARCHS": "x86_64"}, + {"os": "ubuntu-20.04", "CIBW_BUILD": "cp*-musllinux_*", "CIBW_ARCHS": "aarch64"}, + {"os": "macos-10.15", "CIBW_BUILD": "cp*", "CIBW_ARCHS": "x86_64"}, + {"os": "windows-2019", "CIBW_BUILD": "cp*", "CIBW_ARCHS": "x86"}, + {"os": "windows-2019", "CIBW_BUILD": "cp*", "CIBW_ARCHS": "AMD64"} + ] + } + + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + + steps: + - id: set-matrix + run: | + if [ $GITHUB_EVENT_NAME == "pull_request" ]; then + echo ::set-output name=matrix::$(echo $MATRIX_PULL_REQUEST | jq -c) + else + echo ::set-output name=matrix::$(echo $MATRIX_WORKFLOW_DISPATCH | jq -c) + fi + + build_wheels: + needs: configure_matrix + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.configure_matrix.outputs.matrix) }} + + env: + CIBW_BUILD_VERBOSITY: 1 + CIBW_BUILD: ${{ matrix.CIBW_BUILD }} + CIBW_ARCHS: ${{ matrix.CIBW_ARCHS }} + + steps: + - uses: actions/checkout@v2 + with: + submodules: true + + - uses: actions/cache@v2 + id: cache-wheel + with: + path: wheelhouse + key: wheel-${{ matrix.CIBW_BUILD }}-${{ matrix.CIBW_ARCHS }}-${{ github.sha }} + + - uses: docker/setup-qemu-action@v1 + if: steps.cache-wheel.outputs.cache-hit != 'true' && runner.os == 'Linux' + + - uses: pypa/cibuildwheel@v2.2.2 + if: steps.cache-wheel.outputs.cache-hit != 'true' + + - uses: actions/upload-artifact@v2 + with: + path: wheelhouse/*.whl + name: wheels + + upload_pypi: + needs: build_wheels + runs-on: ubuntu-latest + if: needs.build_wheels.result == 'success' && github.event.inputs.publish == 'PUBLISH' + + steps: + - uses: actions/download-artifact@v2 + with: + name: wheels + path: wheelhouse + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + packages_dir: wheelhouse + skip_existing: true + + upload_pypi_test: + needs: build_wheels + runs-on: ubuntu-latest + if: needs.build_wheels.result == 'success' && github.event.inputs.publish_test == 'PUBLISH_TEST' + + steps: + - uses: actions/download-artifact@v2 + with: + name: wheels + path: wheelhouse + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + packages_dir: wheelhouse + skip_existing: true + repository_url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..397ef60 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,41 @@ +name: Documentation + +on: + push: + branches: [ RC_1_2 RC_2_0 master ] + pull_request: + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }} + cancel-in-progress: true + +jobs: + + docs: + name: Docs + runs-on: ubuntu-20.04 + + steps: + - name: checkout + uses: actions/checkout@v2 + + - name: update package lists + continue-on-error: true + run: | + sudo apt update + + - name: install dependencies + run: | + sudo apt install python-docutils python-pygments python-pil gsfonts inkscape icoutils graphviz hunspell imagemagick + python -m pip install aafigure + ~/.local/bin/aafigure --version + + - name: spell-check + run: | + cd docs + make AAFIGURE=~/.local/bin/aafigure RST2HTML=rst2html spell-check html + + - name: build docs + run: | + cd docs + make AAFIGURE=~/.local/bin/aafigure RST2HTML=rst2html diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml new file mode 100644 index 0000000..6710e5d --- /dev/null +++ b/.github/workflows/linux.yml @@ -0,0 +1,340 @@ +name: Linux + +on: + push: + branches: [ RC_1_2 RC_2_0 master ] + pull_request: + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }} + cancel-in-progress: true + +jobs: + + pre-commit: + # TODO: matrix across python version and os + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - uses: actions/setup-python@v2 + with: + python-version: 3.6 + - uses: pre-commit/action@v2.0.0 + + build: + name: build + runs-on: ubuntu-20.04 + continue-on-error: true + + strategy: + matrix: + config: [ asio-debugging=on picker-debugging=on, extensions=off logging=off streaming=off super-seeding=off share-mode=off predictive-pieces=off dht=off alert-msg=off encryption=off mutable-torrents=off deprecated-functions=off, crypto=gcrypt, mmap-disk-io=off ] + + steps: + - name: checkout + uses: actions/checkout@v2 + with: + submodules: true + + - name: update package lists + continue-on-error: true + run: | + sudo apt update + + - uses: Chocobo1/setup-ccache-action@v1 + with: + update_packager_index: false + override_cache_key: ccache-linux-build-${{ github.base_ref }}-${{ matrix.config }} + ccache_options: | + max_size=5G + + - name: install boost + run: | + sudo apt install libboost-tools-dev libboost-dev libboost-system-dev + echo "using gcc ;" >>~/user-config.jam + + - name: install gcrypt + if: ${{ contains(matrix.config, 'crypto=gcrypt') }} + run: sudo apt install libgcrypt20-dev + + - name: build library + run: | + b2 ${{ matrix.config }} cxxstd=14 + + - name: build examples + run: | + cd examples + b2 ${{ matrix.config }} + + - name: build tools + run: | + cd tools + b2 ${{ matrix.config }} warnings-as-errors=on + + + + fuzzers: + name: Fuzzers + runs-on: ubuntu-20.04 + continue-on-error: true + + steps: + - name: checkout + uses: actions/checkout@v2 + with: + submodules: true + + - name: update package lists + continue-on-error: true + run: | + sudo apt update + + - uses: Chocobo1/setup-ccache-action@v1 + with: + update_packager_index: false + override_cache_key: ccache-linux-fuzzers-${{ github.base_ref }} + ccache_options: | + max_size=500M + + - name: install clang-9 + continue-on-error: true + run: | + sudo apt install clang-9 + + - name: install boost + run: | + sudo apt install libboost-tools-dev libboost-dev libboost-system-dev + echo "using clang : 9 : clang++-9 ;" >>~/user-config.jam + + - name: build fuzzers + run: | + cd fuzzers + b2 clang cxxstd=14 warnings-as-errors=on + + + + check_headers: + name: check headers + runs-on: ubuntu-20.04 + + steps: + - name: checkout + uses: actions/checkout@v2 + with: + submodules: true + + - name: update package lists + continue-on-error: true + run: | + sudo apt update + + - uses: Chocobo1/setup-ccache-action@v1 + with: + update_packager_index: false + override_cache_key: ccache-linux-check-headers-${{ github.base_ref }} + ccache_options: | + max_size=500M + + - name: install boost + run: | + sudo apt install libboost-tools-dev libboost-dev libboost-system-dev + echo "using gcc ;" >>~/user-config.jam + + - name: compile header files individually + run: | + b2 check-headers cxxstd=14 warnings-as-errors=on + + + + clang_tidy: + name: clang-tidy + runs-on: ubuntu-20.04 + + steps: + - name: checkout + uses: actions/checkout@v2 + with: + submodules: true + + - name: update package lists + continue-on-error: true + run: | + sudo apt update + + - uses: Chocobo1/setup-ccache-action@v1 + with: + update_packager_index: false + override_cache_key: ccache-linux-clang-tidy-${{ github.base_ref }} + ccache_options: | + max_size=500M + + - name: install clang-tidy + run: sudo apt install clang-tidy libc++-dev + + - name: install boost + run: | + sudo apt install libboost-tools-dev libboost-dev + echo "using clang_tidy : : clang-tidy \"-checks=-clang-analyzer-core.*,-clang-analyzer-unix.*\" : -std=c++14 -I/usr/local/clang-7.0.0/include/c++/v1 -stdlib=libc++ -stdlib=libc++ ;" >> ~/user-config.jam; + + - name: analyze + run: | + b2 -a clang_tidy + + + + test: + name: Tests + runs-on: ubuntu-20.04 + continue-on-error: true + + strategy: + matrix: + include: + - config: address-sanitizer=norecover undefined-sanitizer=norecover crypto=openssl + - config: toolset=clang logging=off address-sanitizer=norecover undefined-sanitizer=norecover + - config: thread-sanitizer=norecover crypto=openssl release debug-symbols=on + - config: crypto=gnutls + - config: deprecated-functions=off + + steps: + - name: checkout + uses: actions/checkout@v2 + with: + submodules: true + + - name: update package lists + continue-on-error: true + run: | + sudo apt update + + - uses: Chocobo1/setup-ccache-action@v1 + with: + update_packager_index: false + override_cache_key: ccache-linux-tests-${{ matrix.config }}-${{ github.base_ref }} + ccache_options: | + max_size=5G + + - name: install gnutls + if: ${{ contains(matrix.config, 'crypto=gnutls') }} + run: | + sudo apt install libgnutls28-dev + + - name: install clang-10 + continue-on-error: true + run: | + sudo apt install clang-10 + + - name: install boost + run: | + sudo apt install libboost-tools-dev libboost-dev libboost-system-dev + echo "using gcc ;" >>~/user-config.jam + echo "using clang : 10 : clang++-10 ;" >>~/user-config.jam + + - name: build and run tests + run: | + cd test + b2 ${{ matrix.config }} -l500 warnings-as-errors=on debug-iterators=on invariant-checks=full asserts=on deterministic-tests + + - name: run tests (flaky) + uses: nick-invision/retry@v2 + with: + timeout_minutes: 30 + retry_wait_seconds: 4 + max_attempts: 3 + command: (cd test; b2 ${{ matrix.config }} -l500 warnings-as-errors=on debug-iterators=on invariant-checks=full asserts=on) + + + + sim: + name: Simulations + runs-on: ubuntu-20.04 + + steps: + - name: checkout + uses: actions/checkout@v2 + with: + submodules: true + + - name: update package lists + continue-on-error: true + run: | + sudo apt update + + - uses: Chocobo1/setup-ccache-action@v1 + with: + update_packager_index: false + override_cache_key: ccache-linux-simulations-${{ github.base_ref }} + ccache_options: | + max_size=5G + + - name: install boost + run: | + sudo apt install libboost-tools-dev libboost-dev libboost-system-dev + echo "using gcc ;" >>~/user-config.jam + + - name: build and run simulations + run: | + cd simulation + b2 debug-iterators=on invariant-checks=full asserts=on picker-debugging=on + + + dist: + name: build dist + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-20.04 ] + + steps: + - name: checkout + uses: actions/checkout@v2 + with: + submodules: true + + - name: update package lists + continue-on-error: true + run: | + sudo apt update + + - uses: Chocobo1/setup-ccache-action@v1 + with: + update_packager_index: false + override_cache_key: ccache-linux-dist-${{ matrix.os }}-${{ github.base_ref }} + ccache_options: | + max_size=15G + + - name: install dependencies + run: | + sudo apt install libboost-tools-dev libboost-python-dev libboost-dev libboost-system-dev + sudo apt install python-docutils python-pygments python-pil gsfonts inkscape icoutils graphviz hunspell imagemagick python3-setuptools + python3 -m pip install aafigure + echo "using gcc ;" >>~/user-config.jam + + - name: build tarball + run: AAFIGURE=~/.local/bin/aafigure RST2HTML=rst2html make dist + + - uses: actions/upload-artifact@v2 + with: + name: tarball + path: libtorrent-rasterbar-*.tar.gz + + - name: test-tarball (b2 install) + run: | + tar xvzf libtorrent-rasterbar-*.tar.gz + cd libtorrent-rasterbar-*/ + b2 install cxxstd=14 --prefix=test-install-root + cat test-install-root/lib/pkgconfig/libtorrent-rasterbar.pc + + - name: test-tarball (b2 tests) + run: | + cd libtorrent-rasterbar-*/test + b2 testing.execute=off + b2 test_torrent_info + + - name: test-tarball (python bindings) + run: | + cd libtorrent-rasterbar-*/ + python3 bindings/python/setup.py build diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 0000000..4b1c5f6 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,144 @@ +name: MacOS + +on: + push: + branches: [ RC_1_2 RC_2_0 master ] + pull_request: + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }} + cancel-in-progress: true + +jobs: + + test: + name: Tests + runs-on: macos-latest + continue-on-error: true + + strategy: + matrix: + config: [ crypto=built-in, deprecated-functions=off ] + + steps: + - name: checkout + uses: actions/checkout@v2 + with: + submodules: true + + - uses: Chocobo1/setup-ccache-action@v1 + with: + override_cache_key: ccache-macos-test-${{ matrix.config }}-${{ github.base_ref }} + ccache_options: | + max_size=1G + + - name: install boost + run: | + brew install boost-build boost openssl + echo "using darwin ;" >>~/user-config.jam + + - name: build and run tests + run: (cd test; b2 ${{ matrix.config }} -l400 warnings-as-errors=on debug-iterators=on invariant-checks=full asserts=on deterministic-tests) + + - name: run tests (flaky) + uses: nick-invision/retry@v2 + with: + timeout_minutes: 30 + retry_wait_seconds: 1 + max_attempts: 3 + command: (cd test; b2 ${{ matrix.config }} -l400 warnings-as-errors=on debug-iterators=on invariant-checks=full asserts=on) + + + sim: + name: Simulations + runs-on: macos-latest + + steps: + - name: checkout + uses: actions/checkout@v2 + with: + submodules: true + + - uses: Chocobo1/setup-ccache-action@v1 + with: + override_cache_key: ccache-macos-sim-${{ github.base_ref }} + ccache_options: | + max_size=1G + + - name: install boost + run: | + brew install boost-build boost openssl + echo "using darwin ;" >>~/user-config.jam + + - name: build and run simulations + run: | + cd simulation + b2 -l400 debug-iterators=on invariant-checks=full asserts=on + + + build: + name: Build + runs-on: macos-latest + continue-on-error: true + + strategy: + matrix: + config: [ crypto=built-in, release ] + + steps: + - name: checkout + uses: actions/checkout@v2 + with: + submodules: true + + - uses: Chocobo1/setup-ccache-action@v1 + with: + override_cache_key: ccache-macos-build-${{ matrix.config }}-${{ github.base_ref }} + ccache_options: | + max_size=1G + + - name: install boost + run: | + brew install boost-build boost openssl + echo "using darwin ;" >>~/user-config.jam + + - name: build library + run: | + b2 ${{ matrix.config }} -l400 warnings-as-errors=on cxxstd=14 + + + ios_build: + name: Build iOS + runs-on: macos-latest + continue-on-error: true + + steps: + - name: checkout + uses: actions/checkout@v2.3.3 + with: + submodules: true + + - name: install boost + run: | + brew install boost-build boost + echo "using darwin : ios_sim : clang++ : -Wno-deprecated-declarations + \"-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk\" + -mios-simulator-version-min=7 + -fobjc-abi-version=2 + \"-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk\" + -mios-simulator-version-min=7 + -fobjc-abi-version=2 ;" >>~/user-config.jam; + + echo "using darwin : ios : clang++ : -Wno-deprecated-declarations + \"-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk\" + -mios-version-min=7 + \"-arch armv7\" + -fobjc-abi-version=2 + \"-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk\" + -mios-version-min=7 + \"-arch armv7\" + -fobjc-abi-version=2 ;" >>~/user-config.jam; + + - name: build library + run: | + b2 -l400 cxxstd=14 darwin-ios darwin-ios_sim address-model=64 link=static diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 0000000..a027176 --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,112 @@ +name: Python bindings + +on: + push: + branches: [ RC_1_2 RC_2_0 master ] + pull_request: + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }} + cancel-in-progress: true + +jobs: + + test: + name: build + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-20.04, macos-latest, windows-latest ] + + steps: + - uses: actions/checkout@v2 + with: + submodules: true + + - name: dependencies (MacOS) + if: runner.os == 'macOS' + run: | + brew install boost-build boost boost-python3 python@3.9 openssl + echo "using darwin ;" >>~/user-config.jam + + - name: update package lists (linux) + if: runner.os == 'Linux' + continue-on-error: true + run: | + sudo apt update + + - uses: Chocobo1/setup-ccache-action@v1 + if: runner.os != 'Windows' + with: + update_packager_index: false + override_cache_key: ccache-python-${{ matrix.os }}-${{ github.base_ref }} + ccache_options: | + max_size=500M + + - name: dependencies (linux) + if: runner.os == 'Linux' + run: | + sudo apt install libboost-tools-dev libboost-python-dev libboost-dev libboost-system-dev python3 python3-setuptools libssl-dev + + - name: install boost (windows) + if: runner.os == 'Windows' + shell: cmd + run: | + git clone --depth=1 --recurse-submodules -j10 --branch=boost-1.78.0 https://github.com/boostorg/boost.git + cd boost + bootstrap.bat + + - name: boost headers (windows) + if: runner.os == 'Windows' + shell: cmd + run: | + cd boost + .\b2 headers + + - name: install openssl (windows) + if: runner.os == 'Windows' + uses: nick-invision/retry@v2 + with: + shell: cmd + timeout_minutes: 5 + retry_wait_seconds: 4 + max_attempts: 3 + command: choco install openssl --limitoutput --no-progress + + - name: build/install (windows) + if: runner.os == 'Windows' + shell: cmd + run: | + set BOOST_ROOT=%CD%\boost + set BOOST_BUILD_PATH=%BOOST_ROOT%\tools\build + set PATH=%BOOST_ROOT%;%PATH% + + cd bindings\python + python setup.py build_ext install --user --prefix= + + - name: tests (windows) + if: runner.os == 'Windows' + shell: cmd + run: | + cd bindings\python + python test.py + + - name: build/install (Linux) + if: runner.os == 'Linux' + run: | + cd bindings/python + python3 setup.py build_ext install --user --prefix= + + - name: build/install (MacOS) + if: runner.os == 'macOS' + run: | + # Install to Homebrew's python site-packages. no need for --user and --prefix + cd bindings/python + python3 setup.py build_ext install --install-lib $(brew --prefix)/lib/python3.9/site-packages + + - name: tests + if: runner.os != 'Windows' + run: | + cd bindings/python + python3 test.py diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 0000000..e10c998 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,178 @@ +name: Windows + +on: + push: + branches: [ RC_1_2 RC_2_0 master ] + pull_request: + +defaults: + run: + shell: cmd + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }} + cancel-in-progress: true + +jobs: + + tests: + name: Test + runs-on: windows-latest + continue-on-error: true + + strategy: + matrix: + include: + - config: address-model=32 crypto=built-in + - config: address-model=64 + - config: release + + steps: + - name: checkout + uses: actions/checkout@v2 + with: + submodules: true + + - name: install openssl (64 bit) + uses: nick-invision/retry@v2 + with: + timeout_minutes: 30 + retry_wait_seconds: 4 + max_attempts: 3 + command: choco install openssl --limitoutput --no-progress + + - name: install boost + run: | + git clone --depth=1 --recurse-submodules -j10 --branch=boost-1.78.0 https://github.com/boostorg/boost.git + cd boost + bootstrap.bat + + - name: boost headers + run: | + cd boost + .\b2 headers + + - name: tests (deterministic) + run: | + set BOOST_ROOT=%CD%\boost + set PATH=%BOOST_ROOT%;%PATH% + set PYTHON_INTERPRETER=python + cd test + b2 -l400 warnings=all warnings-as-errors=on ${{ matrix.config }} deterministic-tests + + + - name: tests (flaky) + run: | + set BOOST_ROOT=%CD%\boost + set PATH=%BOOST_ROOT%;%PATH% + set PYTHON_INTERPRETER=python + cd test + set c=3 + :retry + if %c%==0 exit /B 1 + set /a c = %c% -1 + b2 -l400 warnings=all warnings-as-errors=on ${{ matrix.config }} + if %errorlevel%==0 exit /B 0 + if %c% gtr 0 goto retry + exit /B 1 + + simulations: + name: Simulations + runs-on: windows-2019 + + steps: + - name: checkout + uses: actions/checkout@v2.3.3 + with: + submodules: true + + - name: install boost + run: | + git clone --depth=1 --recurse-submodules -j10 --branch=boost-1.78.0 https://github.com/boostorg/boost.git + cd boost + bootstrap.bat + + - name: boost headers + run: | + cd boost + .\b2 headers + + # debug iterators are turned off here because msvc has issues with noexcept + # specifiers when debug iterators are enabled. Specifically, constructors that + # allocate memory are still marked as noexcept. That results in program + # termination + # the IOCP backend in asio appears to have an issue where it hangs under + # certain unexpected terminations (through exceptions) + - name: build sims + run: | + set BOOST_ROOT=%CD%\boost + set PATH=%BOOST_ROOT%;%PATH% + cd simulation + b2 --hash release address-model=64 link=static debug-iterators=off invariant-checks=on define=BOOST_ASIO_DISABLE_IOCP asserts=on testing.execute=off + + - name: run sims + run: | + set BOOST_ROOT=%CD%\boost + set PATH=%BOOST_ROOT%;%PATH% + cd simulation + b2 --hash -l700 release address-model=64 link=static debug-iterators=off invariant-checks=on define=BOOST_ASIO_DISABLE_IOCP asserts=on + + build: + name: Build + runs-on: windows-2019 + continue-on-error: true + + strategy: + matrix: + include: + - config: asio-debugging=on picker-debugging=on windows-version=vista + - config: windows-api=store windows-version=win10 + - config: deprecated-functions=off + + steps: + - name: checkout + uses: actions/checkout@v2.3.3 + with: + submodules: true + + - name: install boost + run: | + git clone --depth=1 --recurse-submodules -j10 --branch=boost-1.78.0 https://github.com/boostorg/boost.git + cd boost + bootstrap.bat + + - name: install openssl (64 bit) + uses: nick-invision/retry@v2 + with: + timeout_minutes: 30 + retry_wait_seconds: 4 + max_attempts: 3 + command: choco install openssl --limitoutput --no-progress + + - name: boost headers + run: | + cd boost + .\b2 headers + + - name: build library + run: | + set BOOST_ROOT=%CD%\boost + set PATH=%BOOST_ROOT%;%PATH% + b2 ${{ matrix.config }} cxxstd=14 address-model=64 warnings=all warnings-as-errors=on + + - name: build examples + if: ${{ ! contains(matrix.config, 'windows-api=store') }} + run: | + set BOOST_ROOT=%CD%\boost + set PATH=%BOOST_ROOT%;%PATH% + cd examples + b2 ${{ matrix.config }} address-model=64 warnings=all warnings-as-errors=on + + - name: build tools + if: ${{ ! contains(matrix.config, 'windows-api=store') }} + run: | + set BOOST_ROOT=%CD%\boost + set PATH=%BOOST_ROOT%;%PATH% + cd tools + b2 ${{ matrix.config }} address-model=64 warnings=all warnings-as-errors=on + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ca1727 --- /dev/null +++ b/.gitignore @@ -0,0 +1,86 @@ +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +libtool + +*.m4 +*.in +*.pc +*.libs +*.deps +*.cache +*.dirstamp +*.swp +*.orig +*.rej + +bin +cmake-build-* +CMakeLists.txt.user +config* +build-aux +test_tmp_* +libtool +docs/reference* +docs/*.html +docs/*-ref.rst + +.DS_Store +.idea +*~ + +# Compile and link flag files +bindings/python/*flags + +# Logs +libtorrent_logs* +*.log + +# Python related files and dirs +*.pyc +dist +*.egg-info + +# python binary +bindings/python/compile_cmd + +# binaries in the examples directory +examples/bt_get +examples/bt_get2 +examples/client_test +examples/connection_tester +examples/custom_storage +examples/dump_torrent +examples/make_torrent +examples/simple_client +examples/stats_counters +examples/upnp_test + +# binaries in the tests directory +tools/dht_put +tools/session_log_alerts diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..99b8201 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "simulation/libsimulator"] + path = simulation/libsimulator + url = https://github.com/arvidn/libsimulator.git +[submodule "deps/try_signal"] + path = deps/try_signal + url = https://github.com/arvidn/try_signal.git +[submodule "deps/asio-gnutls"] + path = deps/asio-gnutls + url = https://github.com/paullouisageneau/boost-asio-gnutls.git diff --git a/.lgtm.yml b/.lgtm.yml new file mode 100644 index 0000000..597a3cc --- /dev/null +++ b/.lgtm.yml @@ -0,0 +1,20 @@ +path_classifiers: + test: + - "test/*.py" + - exclude: "test/*.cpp" + - exclude: "test/*.hpp" + +extraction: + cpp: + prepare: + packages: + - libboost-all-dev + - libssl-dev + - ninja-build + after_prepare: | + wget -q "https://github.com/Kitware/CMake/releases/download/v3.21.2/cmake-3.21.2-linux-x86_64.tar.gz" + tar xzf cmake-3.21.2-linux-x86_64.tar.gz + index: + build_command: | + ./cmake-3.21.2-linux-x86_64/bin/cmake -Dbuild_examples=ON -Dbuild_tests=ON -Dbuild_tools=ON -GNinja . + ./cmake-3.21.2-linux-x86_64/bin/cmake --build . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c7ca30b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,222 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks + +exclude: | + (?x)^( + # These files are vendored from elsewhere, don't process them + LICENSE| + docs/hunspell/.*| + src/ed25519/.*| + include/libtorrent/aux_/route\.h| + test/.*\.xml| + test/ssl/.*\.pem + )$ +default_language_version: + python: python3 +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.4.0 + hooks: + #- id: trailing-whitespace + #- id: end-of-file-fixer + - id: check-yaml + - id: check-case-conflict + - id: check-executables-have-shebangs + exclude: | + (?x)^( + # Enable these later, avoid bloating this PR + tools/run_tests.sh + ) + - id: check-xml + - id: debug-statements + - id: check-symlinks + - id: check-toml +- repo: https://github.com/pappasam/toml-sort + rev: v0.19.0 + hooks: + - id: toml-sort + args: [--all, --in-place] +- repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.7.0 + hooks: + - id: rst-directive-colons + - id: rst-inline-touching-normal +- repo: https://github.com/PyCQA/isort + rev: 5.7.0 + hooks: + - id: isort + exclude: | + (?x)^( + # Enable these later, avoid bloating this PR + bindings/python/client.py| + bindings/python/dummy_data.py| + bindings/python/make_torrent.py| + bindings/python/setup.py| + bindings/python/simple_client.py| + bindings/python/test.py| + docs/gen_reference_doc.py| + examples/run_benchmarks.py| + fuzzers/tools/generate_initial_corpus.py| + fuzzers/tools/unify_corpus_names.py| + test/socks.py| + test/web_server.py| + tools/clean.py| + tools/copyright.py| + tools/dht_flood.py| + tools/libtorrent_lldb.py| + tools/parse_dht_log.py| + tools/parse_dht_rtt.py| + tools/parse_dht_stats.py| + tools/parse_session_stats.py| + tools/parse_utp_log.py| + tools/run_benchmark.py| + tools/set_version.py| + tools/update_copyright.py + )$ +- repo: https://github.com/myint/autoflake + rev: v1.4 + hooks: + - id: autoflake + args: [--in-place, --remove-unused-variables, --remove-all-unused-imports, --remove-duplicate-keys] + # Avoiding PR bloat + exclude: | + (?x)^( + bindings/python/setup.py| + bindings/python/test.py| + tools/benchmark_checking.py| + tools/copyright.py| + tools/gen_convenience_header.py| + tools/libtorrent_lldb.py + ) +- repo: https://github.com/python/black + rev: 20.8b1 + hooks: + - id: black + # Avoiding PR bloat + exclude: | + (?x)^( + bindings/python/client.py| + bindings/python/dummy_data.py| + bindings/python/make_torrent.py| + bindings/python/setup.py| + bindings/python/simple_client.py| + bindings/python/test.py| + docs/filter-rst.py| + docs/gen_settings_doc.py| + docs/gen_reference_doc.py| + docs/gen_stats_doc.py| + docs/gen_todo.py| + examples/run_benchmarks.py| + fuzzers/tools/generate_initial_corpus.py| + fuzzers/tools/unify_corpus_names.py| + setup.py| + test/http_proxy.py| + test/socks.py| + test/web_server.py| + tools/benchmark_checking.py| + tools/clean.py| + tools/copyright.py| + tools/dht_flood.py| + tools/gen_convenience_header.py| + tools/gen_fwd.py| + tools/libtorrent_lldb.py| + tools/parse_dht_log.py| + tools/parse_dht_rtt.py| + tools/parse_dht_stats.py| + tools/parse_lookup_log.py| + tools/parse_peer_log.py| + tools/parse_sample.py| + tools/parse_session_stats.py| + tools/parse_utp_log.py| + tools/run_benchmark.py| + tools/set_version.py| + tools/update_copyright.py + )$ + # black doesn't run on *.pyi files by default, for reasons + - id: black + name: black (pyi) + types: [pyi] +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.800 + hooks: + - id: mypy + # Avoiding PR bloat + exclude: | + (?x)^( + bindings/python/client.py| + bindings/python/dummy_data.py| + bindings/python/make_torrent.py| + bindings/python/setup.py| + bindings/python/test.py| + docs/filter-rst.py| + docs/gen_reference_doc.py| + docs/gen_settings_doc.py| + docs/gen_stats_doc.py| + docs/gen_todo.py| + examples/run_benchmarks.py| + fuzzers/tools/generate_initial_corpus.py| + setup.py| + test/http_proxy.py| + test/socks.py| + test/web_server.py| + tools/benchmark_checking.py| + tools/clean.py| + tools/copyright.py| + tools/dht_flood.py| + tools/gen_convenience_header.py| + tools/gen_fwd.py| + tools/libtorrent_lldb.py| + tools/parse_dht_log.py| + tools/parse_dht_stats.py| + tools/parse_lookup_log.py| + tools/parse_peer_log.py| + tools/parse_sample.py| + tools/parse_session_stats.py| + tools/parse_utp_log.py| + tools/run_benchmark.py| + tools/set_version.py| + tools/update_copyright.py + )$ +- repo: https://gitlab.com/pycqa/flake8.git + rev: 3.8.4 + hooks: + - id: flake8 + exclude: | + (?x)^( + # Enable these later, avoid bloating this PR + bindings/python/client.py| + bindings/python/make_torrent.py| + bindings/python/setup.py| + bindings/python/test.py| + docs/gen_settings_doc.py| + docs/gen_todo.py| + docs/gen_reference_doc.py| + docs/gen_stats_doc.py| + examples/run_benchmarks.py| + fuzzers/tools/generate_initial_corpus.py| + test/http_proxy.py| + test/socks.py| + test/web_server.py| + tools/benchmark_checking.py| + tools/dht_flood.py| + tools/gen_convenience_header.py| + tools/gen_fwd.py| + tools/libtorrent_lldb.py| + tools/parse_dht_stats.py| + tools/parse_dht_log.py| + tools/parse_lookup_log.py| + tools/parse_peer_log.py| + tools/parse_sample.py| + tools/parse_session_stats.py| + tools/parse_utp_log.py| + tools/run_benchmark.py| + tools/set_version.py| + tools/update_copyright.py + )$ +#- repo: local +# hooks: +# - id: gen_fwd +# name: gen_fwd +# language: system +# entry: ./tools/gen_fwd.py +# always_run: true diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..c8a2154 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,25 @@ +Written by Arvid Norberg. Copyright (c) 2003-2021 + +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 + +Thanks to Reimond Retz for bugfixes, suggestions and testing + +Thanks to University of Umeå for providing development and test hardware. + +Project is hosted by Github + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f85276f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,984 @@ +cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR) # Configurable policies: <= CMP0097 + +cmake_policy(SET CMP0091 NEW) +cmake_policy(SET CMP0092 NEW) + +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 "${VER_MAJOR}.${VER_MINOR}") + +include(GNUInstallDirs) +include(GeneratePkgConfig) + +set(libtorrent_include_files + add_torrent_params.hpp + address.hpp + alert.hpp + alert_types.hpp + announce_entry.hpp + assert.hpp + bdecode.hpp + bencode.hpp + bitfield.hpp + bloom_filter.hpp + bt_peer_connection.hpp + choker.hpp + client_data.hpp + close_reason.hpp + config.hpp + copy_ptr.hpp + crc32c.hpp + create_torrent.hpp + deadline_timer.hpp + debug.hpp + disk_buffer_holder.hpp + disk_interface.hpp + disk_observer.hpp + download_priority.hpp + entry.hpp + enum_net.hpp + error.hpp + error_code.hpp + extensions.hpp + file.hpp + file_storage.hpp + fingerprint.hpp + flags.hpp + fwd.hpp + gzip.hpp + hash_picker.hpp + hasher.hpp + hex.hpp + http_connection.hpp + http_parser.hpp + http_seed_connection.hpp + http_stream.hpp + http_tracker_connection.hpp + i2p_stream.hpp + identify_client.hpp + index_range.hpp + io.hpp + io_service.hpp + ip_filter.hpp + ip_voter.hpp + libtorrent.hpp + link.hpp + lsd.hpp + magnet_uri.hpp + mmap_disk_io.hpp + mmap_storage.hpp + natpmp.hpp + netlink.hpp + operations.hpp + optional.hpp + parse_url.hpp + part_file.hpp + peer.hpp + peer_class.hpp + peer_class_set.hpp + peer_class_type_filter.hpp + peer_connection.hpp + peer_connection_handle.hpp + peer_connection_interface.hpp + peer_id.hpp + peer_info.hpp + peer_list.hpp + peer_request.hpp + performance_counters.hpp + pex_flags.hpp + piece_block.hpp + piece_block_progress.hpp + piece_picker.hpp + platform_util.hpp + portmap.hpp + proxy_base.hpp + puff.hpp + random.hpp + read_resume_data.hpp + request_blocks.hpp + resolve_links.hpp + session.hpp + session_handle.hpp + session_params.hpp + session_settings.hpp + session_stats.hpp + session_status.hpp + session_types.hpp + settings_pack.hpp + sha1.hpp + sha1_hash.hpp + sha256.hpp + sliding_average.hpp + socket.hpp + socket_io.hpp + socket_type.hpp + socks5_stream.hpp + span.hpp + ssl.hpp + ssl_stream.hpp + stack_allocator.hpp + stat.hpp + stat_cache.hpp + storage_defs.hpp + string_util.hpp + string_view.hpp + tailqueue.hpp + time.hpp + torrent.hpp + torrent_flags.hpp + torrent_handle.hpp + torrent_info.hpp + torrent_peer.hpp + torrent_peer_allocator.hpp + torrent_status.hpp + tracker_manager.hpp + truncate.hpp + udp_socket.hpp + udp_tracker_connection.hpp + union_endpoint.hpp + units.hpp + upnp.hpp + utf8.hpp + vector_utils.hpp + version.hpp + web_connection_base.hpp + web_peer_connection.hpp + write_resume_data.hpp + xml_parse.hpp +) + +set(libtorrent_kademlia_include_files + announce_flags.hpp + dht_observer.hpp + dht_settings.hpp + dht_state.hpp + dht_storage.hpp + dht_tracker.hpp + direct_request.hpp + dos_blocker.hpp + ed25519.hpp + find_data.hpp + get_item.hpp + get_peers.hpp + io.hpp + item.hpp + msg.hpp + node.hpp + node_entry.hpp + node_id.hpp + observer.hpp + put_data.hpp + refresh.hpp + routing_table.hpp + rpc_manager.hpp + sample_infohashes.hpp + traversal_algorithm.hpp + types.hpp +) + +set(libtorrent_extensions_include_files + smart_ban.hpp + ut_metadata.hpp + ut_pex.hpp +) + +set(libtorrent_aux_include_files + alert_manager.hpp + aligned_storage.hpp + aligned_union.hpp + alloca.hpp + allocating_handler.hpp + apply_pad_files.hpp + array.hpp + bandwidth_limit.hpp + bandwidth_manager.hpp + bandwidth_queue_entry.hpp + bandwidth_socket.hpp + bind_to_device.hpp + buffer.hpp + byteswap.hpp + chained_buffer.hpp + cpuid.hpp + deferred_handler.hpp + deprecated.hpp + deque.hpp + dev_random.hpp + directory.hpp + disable_warnings_pop.hpp + disable_warnings_push.hpp + disk_buffer_pool.hpp + mmap_disk_job.hpp + disk_io_thread_pool.hpp + disk_job_fence.hpp + disk_job_pool.hpp + ed25519.hpp + escape_string.hpp + export.hpp + ffs.hpp + file_progress.hpp + file_view_pool.hpp + has_block.hpp + heterogeneous_queue.hpp + instantiate_connection.hpp + invariant_check.hpp + io.hpp + ip_helpers.hpp + ip_notifier.hpp + keepalive.hpp + listen_socket_handle.hpp + lsd.hpp + merkle.hpp + merkle_tree.hpp + noexcept_movable.hpp + numeric_cast.hpp + packet_buffer.hpp + packet_pool.hpp + path.hpp + polymorphic_socket.hpp + pool.hpp + portmap.hpp + posix_part_file.hpp + proxy_settings.hpp + range.hpp + receive_buffer.hpp + resolver.hpp + resolver_interface.hpp + scope_end.hpp + session_call.hpp + session_impl.hpp + session_interface.hpp + session_settings.hpp + session_udp_sockets.hpp + set_socket_buffer.hpp + set_traffic_class.hpp + set_traffic_class.hpp + socket_type.hpp + storage_free_list.hpp + storage_utils.hpp + string_ptr.hpp + strview_less.hpp + suggest_piece.hpp + throw.hpp + time.hpp + timestamp_history.hpp + torrent_impl.hpp + torrent_list.hpp + unique_ptr.hpp + utp_socket_manager.hpp + utp_stream.hpp + vector.hpp + win_crypto_provider.hpp + win_util.hpp +) + +set(try_signal_include_files + try_signal + signal_error_code + try_signal_mingw + try_signal_msvc + try_signal_posix +) + +set(sources + add_torrent_params.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 + bt_peer_connection.cpp + chained_buffer.cpp + choker.cpp + close_reason.cpp + copy_file.cpp + cpuid.cpp + crc32c.cpp + create_torrent.cpp + directory.cpp + disabled_disk_io.cpp + disk_buffer_holder.cpp + disk_buffer_pool.cpp + disk_interface.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 + ffs.cpp + file.cpp + file_progress.cpp + file_storage.cpp + file_view_pool.cpp + fingerprint.cpp + generate_peer_id.cpp + gzip.cpp + hash_picker.cpp + hasher.cpp + hex.cpp + http_connection.cpp + http_parser.cpp + http_seed_connection.cpp + http_tracker_connection.cpp + i2p_stream.cpp + identify_client.cpp + instantiate_connection.cpp + ip_filter.cpp + ip_helpers.cpp + ip_notifier.cpp + ip_voter.cpp + listen_socket_handle.cpp + lsd.cpp + magnet_uri.cpp + merkle.cpp + merkle_tree.cpp + mmap.cpp + mmap_disk_io.cpp + mmap_disk_job.cpp + mmap_storage.cpp + natpmp.cpp + packet_buffer.cpp + parse_url.cpp + part_file.cpp + path.cpp + peer_class.cpp + peer_class_set.cpp + peer_connection.cpp + peer_connection_handle.cpp + peer_info.cpp + peer_list.cpp + performance_counters.cpp + piece_picker.cpp + platform_util.cpp + posix_disk_io.cpp + posix_part_file.cpp + posix_storage.cpp + proxy_base.cpp + proxy_settings.cpp + puff.cpp + random.cpp + read_resume_data.cpp + receive_buffer.cpp + request_blocks.cpp + resolve_links.cpp + resolver.cpp + session.cpp + session_call.cpp + session_handle.cpp + session_impl.cpp + session_params.cpp + session_settings.cpp + session_stats.cpp + settings_pack.cpp + sha1.cpp + sha1_hash.cpp + sha256.cpp + socket_io.cpp + socket_type.cpp + socks5_stream.cpp + ssl.cpp + stack_allocator.cpp + stat.cpp + stat_cache.cpp + storage_utils.cpp + string_util.cpp + time.cpp + timestamp_history.cpp + torrent.cpp + torrent_handle.cpp + torrent_info.cpp + torrent_peer.cpp + torrent_peer_allocator.cpp + torrent_status.cpp + tracker_manager.cpp + truncate.cpp + udp_socket.cpp + udp_tracker_connection.cpp + upnp.cpp + utf8.cpp + utp_socket_manager.cpp + utp_stream.cpp + version.cpp + web_connection_base.cpp + web_peer_connection.cpp + write_resume_data.cpp + xml_parse.cpp + +# -- extensions -- + smart_ban.cpp + ut_pex.cpp + ut_metadata.cpp +) + +# -- kademlia -- +set(kademlia_sources + dht_settings.cpp + dht_state.cpp + dht_storage.cpp + dht_tracker.cpp + dos_blocker.cpp + ed25519.cpp + find_data.cpp + get_item.cpp + get_peers.cpp + item.cpp + msg.cpp + node.cpp + node_entry.cpp + node_id.cpp + put_data.cpp + refresh.cpp + routing_table.cpp + rpc_manager.cpp + sample_infohashes.cpp + traversal_algorithm.cpp +) + +# -- ed25519 -- +set(ed25519_sources + add_scalar.cpp + fe.cpp + ge.cpp + key_exchange.cpp + keypair.cpp + sc.cpp + sign.cpp + verify.cpp + sha512.cpp + hasher512.cpp +) + +set(try_signal_sources + try_signal.cpp + signal_error_code.cpp +) + +list(TRANSFORM sources PREPEND "src/") +list(TRANSFORM kademlia_sources PREPEND "src/kademlia/") +list(TRANSFORM ed25519_sources PREPEND "src/ed25519/") +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/") +list(TRANSFORM try_signal_sources PREPEND "deps/try_signal/") + +# 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) + +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_public_dependency(Threads REQUIRED) + +if(CMAKE_CXX_COMPILER_ID MATCHES Clang) + add_compile_options( + -Weverything + -Wno-c++98-compat-pedantic + -Wno-c++11-compat-pedantic + -Wno-padded + -Wno-alloca + -Wno-global-constructors + -Wno-exit-time-destructors + -Wno-weak-vtables + -Wno-return-std-move-in-c++11 + -Wno-unknown-warning-option + ) +elseif(CMAKE_CXX_COMPILER_ID MATCHES GNU) + add_compile_options( + -Wall + -Wextra + -Wpedantic + -Wvla + -Wno-noexcept-type + -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 + # C4268: 'identifier' : 'const' static/global data initialized + # with compiler generated default constructor fills the object with zeros + /wd4268 + # 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 + # C4503: 'identifier': decorated name length exceeded, name was truncated + /wd4503 + ) +endif() + +if(static_runtime) + if (MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + else() + include(ucm_flags) + ucm_set_runtime(STATIC) + endif() + 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} + ${try_signal_sources} + ${libtorrent_include_files} + ${libtorrent_extensions_include_files} + ${libtorrent_aux_include_files} +) + +# C++ 14 support is required +target_compile_features(torrent-rasterbar + PUBLIC + cxx_std_14 + cxx_attribute_deprecated + cxx_binary_literals + cxx_contextual_conversions + cxx_decltype_auto + cxx_digit_separators + cxx_generic_lambdas + cxx_lambda_init_captures + cxx_relaxed_constexpr + cxx_variable_templates +) + +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 + $ + $ + PRIVATE deps/try_signal +) + +target_compile_definitions(torrent-rasterbar + PUBLIC + $<$:TORRENT_USE_ASSERTS> + BOOST_ASIO_ENABLE_CANCELIO + BOOST_ASIO_NO_DEPRECATED + 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 + PUBLIC + bcrypt mswsock ws2_32 iphlpapi + debug dbghelp crypt32 + ) + + 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 + # https://docs.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170 + /utf-8 + # https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ + /Zc:__cplusplus + /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() + +# check if we need to link with libatomic (not needed on MSVC) +if (NOT Windows) + # TODO: migrate to CheckSourceCompiles in CMake >= 3.19 + include(CheckCXXSourceCompiles) + + set(ATOMICS_TEST_SOURCE [=[ + #include + #include + std::atomic x{0}; + int main() { + x.fetch_add(1, std::memory_order_relaxed); + return 0; + } + ]=]) + string(REPLACE "std::atomic" "std::atomic" ATOMICS8_TEST_SOURCE "${ATOMICS_TEST_SOURCE}") + string(REPLACE "std::atomic" "std::atomic" ATOMICS64_TEST_SOURCE "${ATOMICS_TEST_SOURCE}") + + if(APPLE) + set(CMAKE_REQUIRED_FLAGS "-std=c++11") + endif() + check_cxx_source_compiles("${ATOMICS_TEST_SOURCE}" HAVE_CXX_ATOMICS_WITHOUT_LIB) + check_cxx_source_compiles("${ATOMICS8_TEST_SOURCE}" HAVE_CXX_ATOMICS8_WITHOUT_LIB) + check_cxx_source_compiles("${ATOMICS64_TEST_SOURCE}" HAVE_CXX_ATOMICS64_WITHOUT_LIB) + if((NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) OR (NOT HAVE_CXX_ATOMICS8_WITHOUT_LIB) OR (NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB)) + set(CMAKE_REQUIRED_LIBRARIES "atomic") + check_cxx_source_compiles("${ATOMICS_TEST_SOURCE}" HAVE_CXX_ATOMICS_WITH_LIB) + check_cxx_source_compiles("${ATOMICS8_TEST_SOURCE}" HAVE_CXX_ATOMICS8_WITH_LIB) + check_cxx_source_compiles("${ATOMICS64_TEST_SOURCE}" HAVE_CXX_ATOMICS64_WITH_LIB) + if ((NOT HAVE_CXX_ATOMICS_WITH_LIB) OR (NOT HAVE_CXX_ATOMICS8_WITH_LIB) OR (NOT HAVE_CXX_ATOMICS64_WITH_LIB)) + message(FATAL_ERROR "No native support for std::atomic, or libatomic not found!") + else() + message(STATUS "Linking with libatomic for atomics support") + unset(CMAKE_REQUIRED_LIBRARIES) + target_link_libraries(torrent-rasterbar PUBLIC atomic) + endif() + endif() + if(APPLE) + unset(CMAKE_REQUIRED_FLAGS) + endif() +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) +feature_option(python-egg-info "generate python egg info" OFF) +feature_option(python-install-system-dir "Install python bindings to the system installation directory rather than the CMake installation prefix" OFF) + +# these options require existing target +feature_option(dht "enable support for Mainline DHT" ON) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME deprecated-functions DEFAULT ON + DESCRIPTION "enable deprecated functions for backwards compatibility" DISABLED TORRENT_NO_DEPRECATE) +feature_option(encryption "Enables encryption in libtorrent" ON) +feature_option(exceptions "build with exception support" ON) +feature_option(gnutls "build using GnuTLS instead of OpenSSL" OFF) +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) + +if(NOT gnutls) + 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 + TORRENT_USE_LIBCRYPTO + TORRENT_SSL_PEERS + OPENSSL_NO_SSL2) + endif() +endif() + +if(gnutls OR NOT TARGET OpenSSL::SSL) + find_public_dependency(GnuTLS) + set_package_properties(GnuTLS + PROPERTIES + URL "https://www.gnutls.org/" + DESCRIPTION "GnuTLS is a free software implementation of the TLS and DTLS protocols" + TYPE RECOMMENDED + PURPOSE "Provides HTTPS support to libtorrent" + ) + if(GNUTLS_FOUND) + target_link_libraries(torrent-rasterbar PUBLIC GnuTLS::GnuTLS) + target_compile_definitions(torrent-rasterbar + PUBLIC + TORRENT_USE_GNUTLS + TORRENT_SSL_PEERS) + target_include_directories(torrent-rasterbar PUBLIC + $ + $) + install(DIRECTORY deps/asio-gnutls/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + elseif(gnutls) + message(FATAL_ERROR "GnuTLS library not found") + endif() +endif() + +if (NOT GNUTLS_FOUND AND NOT TARGET OpenSSL::SSL) + 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() +endif() + +if (encryption) + target_sources(torrent-rasterbar PRIVATE include/libtorrent/pe_crypto.hpp src/pe_crypto.cpp) +else() + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_DISABLE_ENCRYPTION) +endif() + +if (dht) + target_sources(torrent-rasterbar PRIVATE + ${libtorrent_kademlia_include_files} + include/libtorrent/aux_/hasher512.hpp + ${kademlia_sources} + ${ed25519_sources} + ) +else() + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_DISABLE_DHT) +endif() + +# Boost +find_public_dependency(Boost REQUIRED) +target_link_libraries(torrent-rasterbar PUBLIC Boost::headers) +if (Boost_MAJOR_VERSION LESS_EQUAL 1 AND Boost_MINOR_VERSION LESS 69) + find_package(Boost REQUIRED COMPONENTS system) + target_link_libraries(torrent-rasterbar PUBLIC Boost::system) +endif() + +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) + +if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") + file(RELATIVE_PATH CMAKE_INSTALL_LIBDIR "${CMAKE_INSTALL_PREFIX}" "${CMAKE_INSTALL_LIBDIR}") +endif() + +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 AnyNewerVersion +) + +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 +) + +if (MSVC) + set_target_properties(torrent-rasterbar + PROPERTIES + PDB_NAME torrent-rasterbar + PDB_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} + COMPILE_PDB_NAME torrent-rasterbar + COMPILE_PDB_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} + ) + + if (static_runtime) + set(PDB_INSTALL_DIR lib) + else() + set(PDB_INSTALL_DIR bin) + endif() + + install( + FILES + ${CMAKE_BINARY_DIR}/torrent-rasterbar.pdb + DESTINATION + ${PDB_INSTALL_DIR} + CONFIGURATIONS + Debug RelWithDebInfo + OPTIONAL + ) +endif() + +# === 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..e747efb --- /dev/null +++ b/COPYING @@ -0,0 +1,28 @@ +Copyright (c) 2003-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 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..f4b24a4 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,2108 @@ +* 2.0.6 released + + * fix issue creating a v2 torrent from torrent_info containing an empty file + * make recheck files also update which files use partfile + * add write_through disk_io_write_mode, which flushes pieces to disk immediately + * improve copy file function to preserve sparse regions (when supported) + * add function to truncate over-sized files part of a torrent + * fix directory creation on windows shared folders + * add flag to make add_files() not record file attributes + * deprecate (unused) allow_partial_disk_writes settings + * fix disk-full error reporting in mmap_disk_io + * fixed similar-torrents feature for v2 torrents + * fix potential unbounded recursion in add_completed_job, in disk I/O + * deprecated (unused) volatile_read_cache setting + * fix part files being marked as hidden on windows + +* 2.0.5 released + + * on windows, explicitly flush memory mapped files periodically + * fix build with WolfSSL + * fix issue where incoming uTP connections were not accepted over SOCKS5 + * fix several issues in handling of checking files of v2 torrents, esp. from magnet links + * make the token limit when parsing metadata from magnet files configurable + * fix issue with stalled pieces on disk full errors + * fix missing python binding for file_progress_flags + * fix torrent_file_with_hashes() to fail when we don't have the piece layers + * restore path character encoding conversion for non UTF-8 locales on linux + * fix use-after-free bug in make_magnet_uri + * add write_torrent_file() to produce a .torrent file from add_torrent_params + * allow loading v2 .torrent files without piece layer + * fix issue with adding v2 torrents with invalid file root hash + +* 2.0.4 released + + * fix piece picker bug causing double-picks with prefer-contiguous enabled + * expose session_params in python bindings + * fix (deprecated) use of add_torrent_params::info_hash + * fix issue creating and loading v2 torrents with empty files. Improves + conformance to BEP52 reference implementation + +* 2.0.3 released + + * add new torrent_file_with_hashes() which includes piece layers for + creating .torrent files + * add file_prio_alert, posted when file priorities are updated + * fix issue where set_piece_hashes() would not propagate file errors + * add missing python binding for event_t + * add work-around for systems without fseeko() (such as Android) + * add convenience header libtorrent/libtorrent.hpp + * increase default max_allowed_in_request_queue + * fix loading non-ascii filenames on windows with torrent_info constructor (2.0 regression) + * add std::hash<> specialization for info_hash_t + * fix integer overflow in hash_picker and properly restrict max file sizes in torrents + * strengthen SSRF mitigation for web seeds + +* 2.0.2 released + + * add v1() and v2() functions to torrent_info + * fix piece_layers() to work for single-piece files + * fix python binding regression in session constructor flags + * fix unaligned piece requests in mmap_storage + * improve client_data_t ergonomics + * fix issue with concurrent access to part files + +* 2.0.1 released + + * fix attribute in single-file v2 torrent creation + * fix padding for empty files in v2 torrent creation + * add function to ask a file_storage whether it's v2 or not + * fix mtime field when creating single-file v2 torrents + * fix performance regression in checking files + * disable use of SetFileValidData() by default (windows). A new setting + allows enabling it + +2.0 released + + * dropped depenency on iconv + * deprecate set_file_hash() in torrent creator, as it's superceded by v2 torrents + * deprecate mutable access to info_section in torrent_info + * removed deprecated lazy_entry/lazy_bdecode + * stats_alert deprecated + * remove bittyrant choking algorithm + * update userdata in add_torrent_params to be type-safe and add to torrent_handle + * add ip_filter to session_params + * added support for wolfSSL for SHA-1 hash and HTTPS (no Torrents over SSL) + * requires OpenSSL minimum version 1.0.0 with SNI support + * deprecated save_state() and load_state() on session in favour of new + write_session_params() and read_session_params() + * added support for BitTorrent v2 (see docs/upgrade_to_2.0.html) + * create_torrent() pad_file_limit parameter removed + * create_torrent() merkle- and optimize-alignment flags removed + * merkle_tree removed from add_torrent_params + * announce_entry expose information per v1 and v2 info-hash announces + * deprecated sha1_hash info_hash members on torrent_removed_alert, + torrent_deleted_alert, torrent_delete_failed_alert and add_torrent_params + * undeprecate error_file_metadata for torrent errors related to its metadata + * remove support for adding a torrent under a UUID (used for previous RSS support) + * remove deprecated feature to add torrents by file:// URL + * remove deprecated feature to download .torrent file from URL + * requires boost >= 1.66 to build + * update networking API to networking TS compatible boost.asio + * overhauled disk I/O subsystem to use memory mapped files (where available) + * libtorrent now requires C++14 to build + * added support for GnuTLS for HTTPS and torrents over SSL + + +1.2.16 released + + * send User-Agent field in anonymous mode + * fix python binding for settings_pack conversion + * fix DHT announce timer issue + * use DSCP_TRAFFIC_TYPE socket option on windows + * update default ToS setting according to RFC 8622 + * keep trying to announce to trackers even when all fail + * don't disable announcing from local endpoints because of temporary failures + * fix issue in parsing UPnP XML response with multiple forwarding services + +1.2.15 released + + * cache DNS lookups for SOCKS5 proxy + * fix stalled pieces on disk-full errors + * fix build configuration issue on NetBSD, OpenBSD and DragonFly + * make UTF-8 sanitization a bit stricter. This will re-write invalid UTF-8 + code points encoding surrogate pairs + * fix restoring last_seen_complete from resume data + * fix issue on MacOS where the DHT was not restarted on a network-up notification + * make remove_torrent flags be treated as flags (instead of an enum) + +1.2.14 released + + * improve handling of seed flag in PEX messages + * fix issue of accruing unlimited DHT node candidates when DHT is disabled + * fix bug in parsing chunked encoding + * fix incorrect reporting of active_duration when entering graceful-pause + * fix python binding for functions taking string_view + * fix python binding for torrent_info constructor overloads + * issue python deprecation warnings for some deprecated functions in the python bindings + * fix python binding for torrent_info::add_url_seed, add_tracker and add_http_seed + +1.2.13 released + + * Use /etc/ssl/cert.pem to validate HTTPS connections on MacOS + * allow no-interest timeouts of peer connections before all connections slots are full + * fix issue where a DHT message would count as an incoming connection + * fix issue when failing to parse outgoing_interfaces setting + * fix super-seeding issue that could cause a segfault + * fix data race in python binding of session::get_torrent_status() + * fix need_save_resume_data() for renaming files, share-mode, upload-mode, + disable- pex, lsd, and dht. + * fix incoming TCP connections when using tracker-only proxy + * fix issue with paths starting with ./ + * fix integer overflow when setting a high DHT upload rate limit + * improve Path MTU discovery logic in uTP + * fix overflow issue when rlimit_nofile is set to infinity + * fix issue in python binding interpreting int settings > INT_MAX + * Fix cxxflags and linkflags injection via environment variables + +1.2.12 released + + * fix loading of DHT node ID from previous session on startup + * use getrandom(), when available, and fall back to /dev/urandom + * fix python binding for "value" in dht put alerts + * fix bug in python binding for dht_put_mutable_item + * fix uTP issue acking FIN packets + * validate HTTPS certificates by default (trackers and web seeds) + * load SSL certificates from windows system certificate store, to authenticate trackers + * introduce mitigation for Server Side Request Forgery in tracker and web seed URLs + * fix error handling for pool allocation failure + +1.2.11 released + + * fix issue with moving the session object + * deprecate torrent_status::allocating. This state is no longer used + * fix bug creating torrents with symbolic links + * remove special case to save metadata in resume data unconditionally when added throught magnet link + * fix bugs in mutable-torrent support (reusing identical files from different torrents) + * fix incorrectly inlined move-assignment of file_storage + * add session::paused flag, and the ability to construct a session in paused mode + * fix session-pause causing tracker announces to fail + * fix peer-exchange flags bug + * allow saving resume data before metadata has been downloaded (for magnet links) + * record blocks in the disk queue as downloaded in the resume data + * fix bug in set_piece_deadline() when set in a zero-priority piece + * fix issue in URL parser, causing issues with certain tracker URLs + * use a different error code than host-unreachable, when skipping tracker announces + +1.2.10 released + + * fix regression in python binding for move_storage() + * improve stat_file() performance on Windows + * fix issue with loading invalid torrents with only 0-sized files + * fix to avoid large stack allocations + +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 info hash 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 being 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..a8c476b --- /dev/null +++ b/Jamfile @@ -0,0 +1,1118 @@ +# 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 ; + +# we need version numbers in the form X.Y.Z in order to trigger the built-in +# support for generating symlinks to the installed library +VERSION = 2.0.6 ; + +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 + /opt/homebrew/lib + ; + + local boost-include-path = + /usr/local/opt/boost/include + /opt/homebrew/include + ; + + # the names are decorated in MacPorts. + lib boost_system : : darwin boost_system-mt $(boost-lib-search-path) + : : $(boost-include-path) ; + + lib boost_system : : boost_system ; +} + +use-project /try_signal : ./deps/try_signal ; + +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 gnutls in $(properties) + { + result += ./deps/asio-gnutls//asio-gnutls ; + result += gnutls/shared ; + } + else if libcrypto in $(properties) + { + result += crypto ; + if linux in $(properties) + { + result += dl ; + } + } + else if wolfssl in $(properties) + { + result += wolfssl ; + } + + 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 ; + } + + # windows xp has no CNG + if ! xp in $(properties) + { + result += bcrypt ; + } + } + + 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 gcc 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 += -Wl,--export-dynamic -rdynamic ; + } + else + { + # backtraces don't work with visibility=hidden, so we only add that in + # the else-block + result += hidden ; + } + + 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 + ; + + if shared in $(properties) + { + result += /try_signal//try_signal/static/on ; + } + else + { + result += /try_signal//try_signal/static ; + } + + 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-c++11-compat-pedantic ; + result += -Wno-padded ; + result += -Wno-alloca ; + result += -Wno-global-constructors ; + result += -Wno-poison-system-directories ; +# 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 += -Wvla ; + result += -Wno-format-zero-length ; + result += -Wno-noexcept-type ; + } + + 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 ; + } + + local VERSION = [ feature.get-values : $(properties) ] ; + if ! $(VERSION) || $(VERSION) < 14 + { + ECHO "libtorrent requires at least C++14. Specify cxxstd=14 or higher" ; + result += no ; + } + + if msvc in $(properties) || intel-win in $(properties) + { + # allow larger .obj files (with more sections) + result += /bigobj ; + # https://docs.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170 + result += /utf-8 ; + } + + if gcc in $(properties) && windows in $(properties) + { + # allow larger .obj files (with more sections) + result += -Wa,-mbig-obj ; + } + + 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) ) + # 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) + { + if ( gcc in $(properties) ) + { + result += -Wl,-Bsymbolic ; + } + } + + return $(result) ; +} + +rule tag ( name : type ? : property-set ) +{ + # we only care about the names of our output static- or shared library, not + # other targets like object files + if $(type) != SHARED_LIB && $(type) != STATIC_LIB + { + return [ virtual-target.add-prefix-and-suffix $(name) : $(type) : $(property-set) ] ; + } + + # static libraries are not versioned + if $(type) = STATIC_LIB + { + return [ virtual-target.add-prefix-and-suffix $(name)-rasterbar : $(type) : $(property-set) ] ; + } + + # shared libraries have the version number before the filename extension on + # windows + if [ $(property-set).get ] in windows cygwin + { + # TODO: add version on windows too + # return [ virtual-target.add-prefix-and-suffix $(name)-rasterbar-$(VERSION) : $(type) : $(property-set) ] ; + return [ virtual-target.add-prefix-and-suffix $(name)-rasterbar : $(type) : $(property-set) ] ; + } + else + { + local name = [ virtual-target.add-prefix-and-suffix $(name)-rasterbar : $(type) : $(property-set) ] ; + return $(name).$(VERSION) ; + } +} + +# 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 + # homebrew on M1 Macs install to /opt/homebrew + OPENSSL_LIB = /opt/homebrew/opt/openssl/lib /usr/local/opt/openssl/lib ; + } + else if windows in $(properties) && $(OPENSSL_LIB) = "" + { + # the de-facto windows installer is https://slproweb.com/products/Win32OpenSSL.html, which installs to c:\Program Files\OpenSSL-Win{32,64}. + # chocolatey appears to use this installer. + local address_model = [ feature.get-values : $(properties) ] ; + OPENSSL_LIB += "C:/Program Files/OpenSSL-Win$(address_model)/lib" ; + OPENSSL_LIB += "C:/Program Files (x86)/OpenSSL-Win$(address_model)/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 + # homebrew on M1 Macs install to /opt/homebrew + OPENSSL_INCLUDE = /opt/homebrew/opt/openssl/include /usr/local/opt/openssl/include ; + } + else if windows in $(properties) && $(OPENSSL_INCLUDE) = "" + { + # the de-facto windows installer is https://slproweb.com/products/Win32OpenSSL.html, which installs to c:\Program Files\OpenSSL-Win{32,64}. + # chocolatey appears to use this installer. + local address_model = [ feature.get-values : $(properties) ] ; + OPENSSL_INCLUDE += "C:/Program Files/OpenSSL-Win$(address_model)/include" ; + OPENSSL_INCLUDE += "C:/Program Files (x86)/OpenSSL-Win$(address_model)/include" ; + } + + local result ; + result += $(OPENSSL_INCLUDE) ; + return $(result) ; +} + +# the search path to pick up the gnutls libraries from. This is the +# property of those libraries +rule gnutls-lib-path ( properties * ) +{ + local GNUTLS_LIB = [ feature.get-values : $(properties) ] ; + + if darwin in $(properties) && $(GNUTLS_LIB) = "" + { + # on macOS, default to pick up openssl from the homebrew installation + # brew install openssl + # homebrew on M1 Macs install to /opt/homebrew + GNUTLS_LIB = /opt/homebrew/opt/gnutls/lib /usr/local/opt/gnutls/lib ; + } + + local result ; + result += $(GNUTLS_LIB) ; + return $(result) ; +} + +# the include path to pick up gnutls headers from. This is the +# usage-requirement for the gnutls-related libraries +rule gnutls-include-path ( properties * ) +{ + local GNUTLS_INCLUDE = [ feature.get-values : $(properties) ] ; + + if darwin in $(properties) && $(GNUTLS_INCLUDE) = "" + { + # on macOS, default to pick up openssl from the homebrew installation + # brew install openssl + # homebrew on M1 Macs install to /opt/homebrew + GNUTLS_INCLUDE = /opt/homebrew/opt/gnutls/include /usr/local/opt/gnutls/include ; + } + + local result ; + result += $(GNUTLS_INCLUDE) ; + return $(result) ; +} + +# the search path to pick up the wolfssl libraries from. This is the +# property of those libraries +rule wolfssl-lib-path ( properties * ) +{ + local WOLFSSL_LIB = [ feature.get-values : $(properties) ] ; + + if linux in $(properties) && $(WOLFSSL_LIB) = "" + { + # on linux, default ./configure install path + WOLFSSL_LIB = /usr/local/lib ; + } + + local result ; + result += $(WOLFSSL_LIB) ; + return $(result) ; +} + +# the include path to pick up wolfssl headers from. This is the +# usage-requirement for the wolfssl-related libraries +rule wolfssl-include-path ( properties * ) +{ + local WOLFSSL_INCLUDE = [ feature.get-values : $(properties) ] ; + + if linux in $(properties) && $(WOLFSSL_INCLUDE) = "" + { + # on linux, default ./configure install path + WOLFSSL_INCLUDE = /usr/local/include ; + } + + local result ; + result += $(WOLFSSL_INCLUDE) ; + result += $(WOLFSSL_INCLUDE)/wolfssl ; + return $(result) ; +} + +path-constant blacklist-file : tools/sanitizer-blacklist.txt ; + +feature openssl-lib : : free path ; +feature openssl-include : : free path ; + +feature gnutls-lib : : free path ; +feature gnutls-include : : free path ; + +feature wolfssl-lib : : free path ; +feature wolfssl-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 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 mmap-disk-io : on off : composite propagated ; +feature.compose off : TORRENT_HAVE_MMAP=0 TORRENT_HAVE_MAP_VIEW_OF_FILE=0 ; + +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 alert-msg : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_ALERT_MSG ; + +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 : openssl built-in wolfssl gnutls libcrypto gcrypt : composite propagated ; +feature.compose openssl + : TORRENT_USE_LIBCRYPTO + TORRENT_USE_OPENSSL + TORRENT_SSL_PEERS + OPENSSL_NO_SSL2 ; +feature.compose wolfssl + : TORRENT_USE_WOLFSSL + TORRENT_USE_LIBCRYPTO + TORRENT_USE_OPENSSL + OPENSSL_NO_SSL2 + BOOST_ASIO_USE_WOLFSSL + OPENSSL_ALL + WOLFSSL_SHA512 + WOLFSSL_NGINX + WC_NO_HARDEN ; +feature.compose gnutls + : TORRENT_USE_GNUTLS + TORRENT_SSL_PEERS ; +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 ; + +lib advapi32 : : advapi32 ; +lib user32 : : user32 ; +lib shell32 : : shell32 ; +lib gdi32 : : gdi32 ; +lib bcrypt : : bcrypt ; +lib crypt32 : : crypt32 ; +lib z : : shared z ; + +# openssl libraries on windows +# technically, crypt32 is not an OpenSSL dependency, but libtorrent needs it on +# windows to access the system certificate store, for authenticating trackers +alias ssl-deps : advapi32 user32 shell32 gdi32 crypt32 ; + +# 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 : windows 1.1 libcrypto + @openssl-lib-path : : @openssl-include-path ; +lib ssl : ssl-deps : windows 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 gnutls : : gnutls @gnutls-lib-path : : + @gnutls-include-path ; + +lib wolfssl : : wolfssl @wolfssl-lib-path : : + @wolfssl-include-path ; + +lib dbghelp : : dbghelp ; + +# required for networking on beos +lib netkit : : net /boot/system/lib shared ; +lib gcc : : gcc static ; + +# gcrypt 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 + bloom_filter + chained_buffer + choker + close_reason + copy_file + cpuid + crc32c + create_torrent + directory + disk_buffer_holder + disk_buffer_pool + disk_interface + disk_io_thread_pool + disabled_disk_io + disk_job_fence + disk_job_pool + entry + error_code + file_storage + escape_string + string_util + file + path + fingerprint + gzip + hasher + hash_picker + hex + http_connection + http_parser + identify_client + ip_filter + ip_helpers + ip_notifier + ip_voter + listen_socket_handle + merkle + merkle_tree + peer_connection + platform_util + bt_peer_connection + web_connection_base + web_peer_connection + http_seed_connection + peer_connection_handle + i2p_stream + instantiate_connection + natpmp + packet_buffer + piece_picker + peer_list + proxy_base + puff + random + read_resume_data + write_resume_data + receive_buffer + resolve_links + session + session_params + session_handle + session_impl + session_call + settings_pack + sha1 + sha1_hash + sha256 + socket_io + socket_type + socks5_stream + stat + 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_view_pool + lsd + enum_net + 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 + mmap + mmap_disk_io + mmap_disk_job + mmap_storage + posix_disk_io + posix_part_file + posix_storage + ssl + truncate + +# -- 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 + hasher512 + sha512 + ; + +local usage-requirements = + ./include + ./include/libtorrent + 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 + BOOST_ASIO_NO_DEPRECATED + @linking +# msvc optimizations + msvc,release:"/OPT:ICF=5" + msvc,release:"/OPT:REF" + + # disable bogus deprecation warnings on msvc8 + windows:_SCL_SECURE_NO_DEPRECATE + windows:_CRT_SECURE_NO_DEPRECATE + + "$(CXXFLAGS:J= )" + ; + +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:src/ed25519/$(ED25519_SOURCES).cpp + + @building + @warnings + + @tag + + $(usage-requirements) + "$(LDFLAGS:J= )" + + : # default build + multi + 14 + 512 + + : # usage requirements + $(usage-requirements) + shared:TORRENT_LINKING_SHARED + + ; + + +# install rules + +# return libdir and includedir +rule install-paths ( properties * ) +{ + import version ; + + # package.paths was introduced in boost-1.70 (2018.02) + # however, boost build's versioning scheme changed in boost-1.71 to version + # 4.0 + local boost-build-version = [ SPLIT_BY_CHARACTERS [ version.boost-build ] : "-" ] ; + if [ version.version-less [ SPLIT_BY_CHARACTERS $(boost-build-version[1]) : "." ] : 2018 03 ] + { + import option ; + import property ; + local prefix = [ option.get prefix : [ property.select : $(properties) ] ] ; + prefix = $(prefix:G=) ; + # Or some likely defaults if neither is given. + if ! $(prefix) + { + if [ modules.peek : NT ] { prefix = C:\\$(package-name) ; } + else if [ modules.peek : UNIX ] { prefix = /usr/local ; } + } + + return $(prefix)/lib $(prefix)/include ; + } + else + { + local p = [ package.paths libtorrent : $(properties) ] ; + return [ $(p).libdir ] [ $(p).includedir ] ; + } +} + +rule generate-pkg-config ( properties * ) +{ + import property-set ; + import project ; + + local l = [ project.target [ project.module-name "." ] ] ; + + # this is the libtorrent library target + local t = [ $(l).find torrent : . ] ; + + # these are the properties we're using to build it with + local props = [ $(t).generate [ property-set.create $(properties) ] ] ; + local libname = [ $(props[2]).name ] ; + props = $(props[1]) ; + + p = [ install-paths $(properties) ] ; + + local libdir = $(p[0]) ; + local includes = $(p[1]) ; + + local defines ; + local shared_deps ; + local private_deps ; + for d in [ feature.expand $(properties) ] [ $(props).raw ] { + switch $(d) + { + case \TORRENT_* : { + d = [ SPLIT_BY_CHARACTERS $(d) : ">" ] ; + defines += $(d[2]) ; + } + case \BOOST_* : { + d = [ SPLIT_BY_CHARACTERS $(d) : ">" ] ; + defines += $(d[2]) ; + } + case \* : { + d = [ SPLIT_BY_CHARACTERS $(d) : ">" ] ; + d = $(d[2]) ; + if ( [ path.is-rooted $(d) ] ) + { + includes += $(d) ; + } + } + case \* : { + d = [ SPLIT_BY_CHARACTERS $(d) : ">" ] ; + # this is the target + local t = $(d[2]) ; + if [ $(t).type ] = SHARED_LIB + { + local path = [ $(t).path ] ; + if $(path) != "" + { + libdir += $(path) ; + } + shared_deps += [ $(t).name ] ; + } + else if [ $(t).type ] = SEARCHED_LIB + { + local path = [ $(t).search ] ; + if $(path) != "" + { + libdir += $(path) ; + } + shared_deps += [ $(t).name ] ; + } + else if ( [ $(t).type ] = STATIC_LIB ) + { + private_deps += [ $(t).name ] ; + } + } + } + } + + # TODO: use $(libname) in future versions + local config = "Name: libtorrent-rasterbar" + "\nDescription: libtorrent is an open source C++ library implementing the BitTorrent protocol" + "\nURL: https://libtorrent.org" + "\nVersion: $(VERSION)" + "\nLibs:" + " -L\"$(libdir)\"" + " -ltorrent-rasterbar" + " -l$(shared_deps)" + "\nLibs.private:" + " -L\"$(libdir)\"" + " -l$(private_deps)" + "\nCflags:" + " -D$(defines)" + " -I\"$(includes)\"" + "\n" + ; + + local dummy = @("libtorrent-rasterbar.pc":E=$(config)) ; +} + +rule install-pkg-config ( target-name : data * : requirements * ) +{ + import stage ; + local p = [ install-paths $(requirements) ] ; + local libdir = $(p[0]) ; + + stage.install $(target-name) + : $(data) + : $(requirements) $(libdir)/pkgconfig + ; + + import project ; + local c = [ project.current ] ; + local project-module = [ $(c).project-module ] ; + module $(project-module) + { + explicit $(1) ; + } +} + +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 + ; + +install-pkg-config pkg-config-target : libtorrent-rasterbar.pc : @generate-pkg-config ; + +alias install : install-torrent-lib install-cmake-module pkg-config-target ; + +explicit install ; + + +# testing headers targets + +local header_targets ; +for local target in $(headers) +{ + if ! [ path.basename $(target) ] in storage.hpp windows.hpp win_util.hpp win_crypto_provider.hpp torrent_impl.hpp io_service.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 + : 14 ; + 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..05e9627 --- /dev/null +++ b/LICENSE @@ -0,0 +1,183 @@ +Copyright (c) 2003-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. + +------------------------------------------------------------------------------ + +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 + +------------------------------------------------------------------------------ + +bindings/python/src/ + +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. + +------------------------------------------------------------------------------ + +ed25519 implementation based on: + +Copyright (c) 2015 Orson Peters + +This software is provided 'as-is', without any express or implied warranty. In no event will the +authors 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. + +------------------------------------------------------------------------------ + +src/sha1.cpp include/libtorrent/sha1.hpp + +SHA-1 in C +By Steve Reid +100% Public Domain + +------------------------------------------------------------------------------ + +include/libtorrent/_aux/route.h + + * 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. + * + * 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 $ + +------------------------------------------------------------------------------ + +src/sha256.cpp + +SHA-256. Adapted from LibTomCrypt. This code is Public Domain + 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 b/Makefile new file mode 100644 index 0000000..6a6552f --- /dev/null +++ b/Makefile @@ -0,0 +1,1127 @@ +VERSION=2.0.6 + +BUILD_CONFIG=release link=shared crypto=openssl warnings=off address-model=64 + +ifeq (${PREFIX},) +PREFIX=/usr/local/ +endif + +ALL: FORCE + BOOST_ROOT="" b2 ${BUILD_CONFIG} + +python-binding: FORCE + (cd bindings/python; BOOST_ROOT="" b2 ${BUILD_CONFIG} stage_module stage_dependencies) + +examples: FORCE + (cd examples; BOOST_ROOT="" b2 ${BUILD_CONFIG} stage_client_test stage_connection_tester) + +tools: FORCE + (cd tools; BOOST_ROOT="" b2 ${BUILD_CONFIG}) + +install: FORCE + BOOST_ROOT="" b2 ${BUILD_CONFIG} install --prefix=${PREFIX} + +sim: FORCE + (cd simulation; BOOST_ROOT="" b2 $(filter-out crypto=openssl,${BUILD_CONFIG}) crypto=built-in) + +check: FORCE + (cd test; BOOST_ROOT="" b2 crypto=openssl warnings=off) + +clean: FORCE + rm -rf \ + bin \ + examples/bin \ + tools/bin \ + bindings/python/bin \ + test/bin \ + simulation/bin \ + simulator/libsimulator/bin + +DOCS_IMAGES = \ + docs/img/screenshot.png \ + docs/img/screenshot_thumb.png \ + docs/img/cwnd.png \ + docs/img/cwnd_thumb.png \ + docs/img/delays.png \ + docs/img/delays_thumb.png \ + docs/img/our_delay_base.png \ + docs/img/our_delay_base_thumb.png \ + docs/img/read_disk_buffers.png \ + docs/img/read_disk_buffers.diagram \ + docs/img/storage.png \ + docs/img/write_disk_buffers.png \ + docs/img/write_disk_buffers.diagram \ + docs/img/ip_id_v4.png \ + docs/img/ip_id_v6.png \ + docs/img/hash_distribution.png \ + docs/img/complete_bit_prefixes.png \ + docs/img/troubleshooting.dot \ + docs/img/troubleshooting.png \ + docs/img/troubleshooting_thumb.png \ + docs/img/hacking.diagram \ + docs/img/hacking.png \ + docs/img/utp_stack.diagram \ + docs/img/utp_stack.png \ + docs/img/bitcoin.png \ + docs/img/logo-color-text.png \ + docs/img/pp-acceptance-medium.png \ + docs/style.css + +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-ref.html \ + docs/index.html \ + docs/manual-ref.html \ + docs/projects.html \ + docs/python_binding.html \ + docs/tuning-ref.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.rst \ + docs/manual-ref.rst \ + docs/projects.rst \ + docs/python_binding.rst \ + docs/tuning.rst \ + docs/troubleshooting.rst \ + docs/udp_tracker_protocol.rst \ + docs/utp.rst \ + docs/streaming.rst \ + docs/tutorial.rst \ + docs/tutorial-ref.rst \ + docs/header.rst \ + docs/hacking.rst \ + docs/hacking.html \ + docs/todo.html \ + docs/tutorial-ref.html \ + docs/upgrade_to_1.2-ref.html \ + docs/upgrade_to_2.0-ref.html \ + docs/security-audit.html \ + docs/reference.html \ + docs/reference-Core.html \ + docs/reference-DHT.html \ + docs/reference-Session.html \ + docs/reference-Torrent_Handle.html \ + docs/reference-Torrent_Info.html \ + docs/reference-Trackers.html \ + docs/reference-PeerClass.html \ + docs/reference-Torrent_Status.html \ + docs/reference-Stats.html \ + docs/reference-Resume_Data.html \ + docs/reference-Add_Torrent.html \ + docs/reference-Plugins.html \ + docs/reference-Create_Torrents.html \ + docs/reference-Error_Codes.html \ + docs/reference-Storage.html \ + docs/reference-Custom_Storage.html \ + docs/reference-Utility.html \ + docs/reference-Bencoding.html \ + docs/reference-Alerts.html \ + docs/reference-Filter.html \ + docs/reference-Settings.html \ + docs/reference-Bdecoding.html \ + docs/reference-ed25519.html \ + docs/single-page-ref.html + +ED25519_SOURCE = \ + fe.h \ + fixedint.h \ + ge.h \ + precomp_data.h \ + sc.h \ + add_scalar.cpp \ + fe.cpp \ + ge.cpp \ + key_exchange.cpp \ + keypair.cpp \ + sc.cpp \ + sign.cpp \ + verify.cpp \ + sha512.cpp \ + hasher512.cpp \ + +EXTRA_DIST = \ + Jamfile \ + Jamroot.jam \ + project-config.jam \ + Makefile \ + CMakeLists.txt \ + cmake/Modules/FindLibGcrypt.cmake \ + cmake/Modules/GeneratePkgConfig.cmake \ + cmake/Modules/ucm_flags.cmake \ + cmake/Modules/LibtorrentMacros.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 \ + bindings/CMakeLists.txt \ + setup.py \ + LICENSE \ + src/ed25519/LICENSE \ + COPYING \ + AUTHORS \ + NEWS \ + README.rst \ + ChangeLog \ + $(DOCS_PAGES) \ + $(DOCS_IMAGES) + +PYTHON_FILES= \ + CMakeLists.txt \ + Jamfile \ + client.py \ + make_torrent.py \ + setup.py \ + setup.py.cmake.in \ + simple_client.py \ + src/alert.cpp \ + src/boost_python.hpp \ + src/bytes.hpp \ + 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/session.cpp \ + src/session_settings.cpp \ + src/sha1_hash.cpp \ + src/sha256_hash.cpp \ + src/info_hash.cpp \ + src/string.cpp \ + src/torrent_handle.cpp \ + src/torrent_info.cpp \ + src/torrent_status.cpp \ + src/utility.cpp \ + src/version.cpp + +EXAMPLE_FILES= \ + CMakeLists.txt \ + Jamfile \ + bt-get.cpp \ + bt-get2.cpp \ + bt-get3.cpp \ + client_test.cpp \ + cmake/FindLibtorrentRasterbar.cmake \ + connection_tester.cpp \ + dump_torrent.cpp \ + dump_bdecode.cpp \ + make_torrent.cpp \ + print.cpp \ + print.hpp \ + session_view.cpp \ + session_view.hpp \ + simple_client.cpp \ + custom_storage.cpp \ + stats_counters.cpp \ + torrent_view.cpp \ + torrent_view.hpp \ + upnp_test.cpp + +TOOLS_FILES= \ + CMakeLists.txt \ + Jamfile \ + dht_put.cpp \ + dht_sample.cpp \ + disk_io_stress_test.cpp\ + 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 \ + session_log_alerts.cpp + +KADEMLIA_SOURCES = \ + dht_settings.cpp \ + dht_state.cpp \ + dht_storage.cpp \ + dht_tracker.cpp \ + dos_blocker.cpp \ + ed25519.cpp \ + find_data.cpp \ + get_item.cpp \ + get_peers.cpp \ + item.cpp \ + msg.cpp \ + node.cpp \ + node_entry.cpp \ + node_id.cpp \ + put_data.cpp \ + refresh.cpp \ + routing_table.cpp \ + rpc_manager.cpp \ + sample_infohashes.cpp \ + traversal_algorithm.cpp + +SOURCES = \ + add_torrent_params.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 \ + bt_peer_connection.cpp \ + chained_buffer.cpp \ + choker.cpp \ + close_reason.cpp \ + copy_file.cpp \ + cpuid.cpp \ + crc32c.cpp \ + create_torrent.cpp \ + directory.cpp \ + disabled_disk_io.cpp \ + disk_buffer_holder.cpp \ + disk_buffer_pool.cpp \ + disk_interface.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 \ + ffs.cpp \ + file.cpp \ + file_progress.cpp \ + file_storage.cpp \ + file_view_pool.cpp \ + fingerprint.cpp \ + generate_peer_id.cpp \ + gzip.cpp \ + hash_picker.cpp \ + hasher.cpp \ + hex.cpp \ + http_connection.cpp \ + http_parser.cpp \ + http_seed_connection.cpp \ + http_tracker_connection.cpp \ + i2p_stream.cpp \ + identify_client.cpp \ + instantiate_connection.cpp \ + ip_filter.cpp \ + ip_helpers.cpp \ + ip_notifier.cpp \ + ip_voter.cpp \ + listen_socket_handle.cpp \ + lsd.cpp \ + magnet_uri.cpp \ + merkle.cpp \ + merkle_tree.cpp \ + mmap.cpp \ + mmap_disk_io.cpp \ + mmap_disk_job.cpp \ + mmap_storage.cpp \ + natpmp.cpp \ + packet_buffer.cpp \ + parse_url.cpp \ + part_file.cpp \ + path.cpp \ + pe_crypto.cpp \ + peer_class.cpp \ + peer_class_set.cpp \ + peer_connection.cpp \ + peer_connection_handle.cpp \ + peer_info.cpp \ + peer_list.cpp \ + performance_counters.cpp \ + piece_picker.cpp \ + platform_util.cpp \ + posix_disk_io.cpp \ + posix_part_file.cpp \ + posix_storage.cpp \ + proxy_base.cpp \ + proxy_settings.cpp \ + puff.cpp \ + random.cpp \ + read_resume_data.cpp \ + receive_buffer.cpp \ + request_blocks.cpp \ + resolve_links.cpp \ + resolver.cpp \ + session.cpp \ + session_call.cpp \ + session_handle.cpp \ + session_impl.cpp \ + session_params.cpp \ + session_settings.cpp \ + session_stats.cpp \ + settings_pack.cpp \ + sha1.cpp \ + sha1_hash.cpp \ + sha256.cpp \ + smart_ban.cpp \ + socket_io.cpp \ + socket_type.cpp \ + socks5_stream.cpp \ + ssl.cpp \ + stack_allocator.cpp \ + stat.cpp \ + stat_cache.cpp \ + storage_utils.cpp \ + string_util.cpp \ + time.cpp \ + timestamp_history.cpp \ + torrent.cpp \ + torrent_handle.cpp \ + torrent_info.cpp \ + torrent_peer.cpp \ + torrent_peer_allocator.cpp \ + torrent_status.cpp \ + tracker_manager.cpp \ + truncate.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 \ + version.cpp \ + web_connection_base.cpp \ + web_peer_connection.cpp \ + write_resume_data.cpp \ + xml_parse.cpp + +HEADERS = \ + add_torrent_params.hpp \ + address.hpp \ + alert.hpp \ + alert_types.hpp \ + announce_entry.hpp \ + assert.hpp \ + bdecode.hpp \ + bencode.hpp \ + bitfield.hpp \ + bloom_filter.hpp \ + bt_peer_connection.hpp \ + choker.hpp \ + client_data.hpp \ + close_reason.hpp \ + config.hpp \ + copy_ptr.hpp \ + crc32c.hpp \ + create_torrent.hpp \ + deadline_timer.hpp \ + debug.hpp \ + disabled_disk_io.hpp \ + disk_buffer_holder.hpp \ + disk_interface.hpp \ + disk_observer.hpp \ + download_priority.hpp \ + entry.hpp \ + enum_net.hpp \ + error.hpp \ + error_code.hpp \ + extensions.hpp \ + file.hpp \ + file_storage.hpp \ + fingerprint.hpp \ + flags.hpp \ + fwd.hpp \ + gzip.hpp \ + hash_picker.hpp \ + hasher.hpp \ + hex.hpp \ + http_connection.hpp \ + http_parser.hpp \ + http_seed_connection.hpp \ + http_stream.hpp \ + http_tracker_connection.hpp \ + i2p_stream.hpp \ + identify_client.hpp \ + index_range.hpp \ + info_hash.hpp \ + io.hpp \ + io_context.hpp \ + io_service.hpp \ + ip_filter.hpp \ + ip_voter.hpp \ + libtorrent.hpp \ + link.hpp \ + lsd.hpp \ + magnet_uri.hpp \ + mmap_disk_io.hpp \ + mmap_storage.hpp \ + natpmp.hpp \ + netlink.hpp \ + operations.hpp \ + optional.hpp \ + parse_url.hpp \ + part_file.hpp \ + pe_crypto.hpp \ + peer.hpp \ + peer_class.hpp \ + peer_class_set.hpp \ + peer_class_type_filter.hpp \ + peer_connection.hpp \ + peer_connection_handle.hpp \ + peer_connection_interface.hpp \ + peer_id.hpp \ + peer_info.hpp \ + peer_list.hpp \ + peer_request.hpp \ + performance_counters.hpp \ + pex_flags.hpp \ + piece_block.hpp \ + piece_block_progress.hpp \ + piece_picker.hpp \ + platform_util.hpp \ + portmap.hpp \ + posix_disk_io.hpp \ + proxy_base.hpp \ + puff.hpp \ + random.hpp \ + read_resume_data.hpp \ + request_blocks.hpp \ + resolve_links.hpp \ + session.hpp \ + session_handle.hpp \ + session_params.hpp \ + session_settings.hpp \ + session_stats.hpp \ + session_status.hpp \ + session_types.hpp \ + settings_pack.hpp \ + sha1.hpp \ + sha1_hash.hpp \ + sha256.hpp \ + sliding_average.hpp \ + socket.hpp \ + socket_io.hpp \ + socket_type.hpp \ + socks5_stream.hpp \ + span.hpp \ + ssl.hpp \ + ssl_stream.hpp \ + stack_allocator.hpp \ + stat.hpp \ + stat_cache.hpp \ + storage.hpp \ + storage_defs.hpp \ + string_util.hpp \ + string_view.hpp \ + tailqueue.hpp \ + time.hpp \ + torrent.hpp \ + torrent_flags.hpp \ + torrent_handle.hpp \ + torrent_info.hpp \ + torrent_peer.hpp \ + torrent_peer_allocator.hpp \ + torrent_status.hpp \ + tracker_manager.hpp \ + truncate.hpp \ + udp_socket.hpp \ + udp_tracker_connection.hpp \ + union_endpoint.hpp \ + units.hpp \ + upnp.hpp \ + utf8.hpp \ + vector_utils.hpp \ + version.hpp \ + web_connection_base.hpp \ + web_peer_connection.hpp \ + write_resume_data.hpp \ + xml_parse.hpp \ + \ + aux_/alert_manager.hpp \ + aux_/aligned_storage.hpp \ + aux_/aligned_union.hpp \ + aux_/alloca.hpp \ + aux_/allocating_handler.hpp \ + aux_/announce_entry.hpp \ + aux_/apply_pad_files.hpp \ + aux_/array.hpp \ + aux_/bandwidth_limit.hpp \ + aux_/bandwidth_manager.hpp \ + aux_/bandwidth_queue_entry.hpp \ + aux_/bandwidth_socket.hpp \ + aux_/bind_to_device.hpp \ + aux_/buffer.hpp \ + aux_/byteswap.hpp \ + aux_/container_wrapper.hpp \ + aux_/chained_buffer.hpp \ + aux_/cpuid.hpp \ + aux_/deferred_handler.hpp \ + aux_/deprecated.hpp \ + aux_/deque.hpp \ + aux_/dev_random.hpp \ + aux_/directory.hpp \ + aux_/disable_deprecation_warnings_push.hpp \ + aux_/disable_warnings_pop.hpp \ + aux_/disable_warnings_push.hpp \ + aux_/disk_buffer_pool.hpp \ + aux_/disk_io_thread_pool.hpp \ + aux_/disk_job_fence.hpp \ + aux_/disk_job_pool.hpp \ + aux_/ed25519.hpp \ + aux_/escape_string.hpp \ + aux_/export.hpp \ + aux_/ffs.hpp \ + aux_/file_pointer.hpp \ + aux_/file_progress.hpp \ + aux_/file_view_pool.hpp \ + aux_/generate_peer_id.hpp \ + aux_/has_block.hpp \ + aux_/hasher512.hpp \ + aux_/heterogeneous_queue.hpp \ + aux_/instantiate_connection.hpp \ + aux_/invariant_check.hpp \ + aux_/io.hpp \ + aux_/ip_helpers.hpp \ + aux_/ip_notifier.hpp \ + aux_/keepalive.hpp \ + aux_/listen_socket_handle.hpp \ + aux_/lsd.hpp \ + aux_/merkle.hpp \ + aux_/merkle_tree.hpp \ + aux_/mmap.hpp \ + aux_/mmap_disk_job.hpp \ + aux_/noexcept_movable.hpp \ + aux_/numeric_cast.hpp \ + aux_/open_mode.hpp \ + aux_/packet_buffer.hpp \ + aux_/packet_pool.hpp \ + aux_/path.hpp \ + aux_/polymorphic_socket.hpp \ + aux_/pool.hpp \ + aux_/portmap.hpp \ + aux_/posix_part_file.hpp \ + aux_/posix_storage.hpp \ + aux_/proxy_settings.hpp \ + aux_/range.hpp \ + aux_/receive_buffer.hpp \ + aux_/resolver.hpp \ + aux_/resolver_interface.hpp \ + aux_/route.h \ + aux_/scope_end.hpp \ + aux_/session_call.hpp \ + aux_/session_impl.hpp \ + aux_/session_interface.hpp \ + aux_/session_settings.hpp \ + aux_/session_udp_sockets.hpp \ + aux_/set_socket_buffer.hpp \ + aux_/set_traffic_class.hpp \ + aux_/sha512.hpp \ + aux_/socket_type.hpp \ + aux_/storage_free_list.hpp \ + aux_/storage_utils.hpp \ + aux_/store_buffer.hpp \ + aux_/string_ptr.hpp \ + aux_/strview_less.hpp \ + aux_/suggest_piece.hpp \ + aux_/throw.hpp \ + aux_/time.hpp \ + aux_/timestamp_history.hpp \ + aux_/torrent_impl.hpp \ + aux_/torrent_list.hpp \ + aux_/unique_ptr.hpp \ + aux_/utp_socket_manager.hpp \ + aux_/utp_stream.hpp \ + aux_/vector.hpp \ + aux_/windows.hpp \ + aux_/win_cng.hpp \ + aux_/win_crypto_provider.hpp \ + aux_/win_util.hpp \ + \ + extensions/smart_ban.hpp \ + extensions/ut_metadata.hpp \ + extensions/ut_pex.hpp \ + \ + kademlia/announce_flags.hpp \ + kademlia/dht_observer.hpp \ + kademlia/dht_settings.hpp \ + kademlia/dht_state.hpp \ + kademlia/dht_storage.hpp \ + kademlia/dht_tracker.hpp \ + kademlia/direct_request.hpp \ + kademlia/dos_blocker.hpp \ + kademlia/ed25519.hpp \ + kademlia/find_data.hpp \ + kademlia/get_item.hpp \ + kademlia/get_peers.hpp \ + kademlia/io.hpp \ + kademlia/item.hpp \ + kademlia/msg.hpp \ + kademlia/node.hpp \ + kademlia/node_entry.hpp \ + kademlia/node_id.hpp \ + kademlia/observer.hpp \ + kademlia/put_data.hpp \ + kademlia/refresh.hpp \ + kademlia/routing_table.hpp \ + kademlia/rpc_manager.hpp \ + kademlia/sample_infohashes.hpp \ + kademlia/traversal_algorithm.hpp \ + kademlia/types.hpp + +TRY_SIGNAL = \ + signal_error_code.cpp \ + signal_error_code.hpp \ + try_signal.cpp \ + try_signal.hpp \ + try_signal_mingw.hpp \ + try_signal_msvc.hpp \ + try_signal_posix.hpp \ + LICENSE \ + README.rst \ + Jamfile \ + CMakeLists.txt + +ASIO_GNUTLS = \ + LICENSE_1_0.txt \ + Jamfile \ + include/boost/asio/gnutls.hpp \ + include/boost/asio/gnutls \ + include/boost/asio/gnutls/rfc2818_verification.hpp \ + include/boost/asio/gnutls/stream_base.hpp \ + include/boost/asio/gnutls/error.hpp \ + include/boost/asio/gnutls/host_name_verification.hpp \ + include/boost/asio/gnutls/stream.hpp \ + include/boost/asio/gnutls/context_base.hpp \ + include/boost/asio/gnutls/verify_context.hpp \ + include/boost/asio/gnutls/context.hpp \ + README.md \ + test/unit_test.hpp \ + test/gnutls/context_base.cpp \ + test/gnutls/stream_base.cpp \ + test/gnutls/host_name_verification.cpp \ + test/gnutls/error.cpp \ + test/gnutls/Jamfile.v2 \ + test/gnutls/context.cpp \ + test/gnutls/rfc2818_verification.cpp \ + test/gnutls/stream.cpp + +SIM_SOURCES = \ + Jamfile \ + create_torrent.cpp \ + create_torrent.hpp \ + fake_peer.hpp \ + disk_io.hpp \ + disk_io.cpp \ + make_proxy_settings.hpp \ + setup_dht.cpp \ + setup_dht.hpp \ + setup_swarm.cpp \ + setup_swarm.hpp \ + test_auto_manage.cpp \ + test_checking.cpp \ + test_dht.cpp \ + test_dht_bootstrap.cpp \ + test_dht_rate_limit.cpp \ + test_dht_storage.cpp \ + test_error_handling.cpp \ + test_fast_extensions.cpp \ + test_http_connection.cpp \ + test_ip_filter.cpp \ + test_metadata_extension.cpp \ + test_optimistic_unchoke.cpp \ + test_pause.cpp \ + test_pe_crypto.cpp \ + test_peer_connection.cpp \ + test_save_resume.cpp \ + test_session.cpp \ + test_socks5.cpp \ + test_super_seeding.cpp \ + test_swarm.cpp \ + test_thread_pool.cpp \ + test_torrent_status.cpp \ + test_tracker.cpp \ + test_transfer.cpp \ + test_transfer_matrix.cpp \ + test_utp.cpp \ + test_web_seed.cpp \ + transfer_sim.hpp \ + transfer_sim.cpp \ + utils.cpp \ + utils.hpp + +LIBSIM_SOURCES = \ + acceptor.cpp \ + default_config.cpp \ + high_resolution_clock.cpp \ + high_resolution_timer.cpp \ + http_proxy.cpp \ + http_server.cpp \ + io_service.cpp \ + pcap.cpp \ + queue.cpp \ + resolver.cpp \ + simulation.cpp \ + simulator.cpp \ + sink_forwarder.cpp \ + socks_server.cpp \ + tcp_socket.cpp \ + udp_socket.cpp + +LIBSIM_HEADERS = \ + chrono.hpp \ + config.hpp \ + function.hpp \ + handler_allocator.hpp \ + http_proxy.hpp \ + http_server.hpp \ + noexcept_movable.hpp \ + packet.hpp \ + pcap.hpp \ + pop_warnings.hpp \ + push_warnings.hpp \ + queue.hpp \ + simulator.hpp \ + sink.hpp \ + sink_forwarder.hpp \ + socks_server.hpp \ + utils.hpp + +LIBSIM_EXTRA = \ + CMakeLists.txt \ + Jamfile \ + Jamroot.jam \ + LICENSE \ + README.rst + +LIBSIM_TESTS = \ + acceptor.cpp \ + main.cpp \ + multi_accept.cpp \ + multi_homed.cpp \ + null_buffers.cpp \ + parse_request.cpp \ + resolver.cpp \ + timer.cpp \ + udp_socket.cpp \ + catch.hpp + +TEST_SOURCES = \ + enum_if.cpp \ + test_add_torrent.cpp \ + test_alert_manager.cpp \ + test_alert_types.cpp \ + test_alloca.cpp \ + test_apply_pad.cpp \ + test_auto_unchoke.cpp \ + test_bandwidth_limiter.cpp \ + test_bdecode.cpp \ + test_bencoding.cpp \ + test_bitfield.cpp \ + test_bloom_filter.cpp \ + test_buffer.cpp \ + test_checking.cpp \ + test_copy_file.cpp \ + test_crc32.cpp \ + test_create_torrent.cpp \ + test_dht.cpp \ + test_dht_storage.cpp \ + test_direct_dht.cpp \ + test_dos_blocker.cpp \ + test_ed25519.cpp \ + test_enum_net.cpp \ + test_fast_extension.cpp \ + test_fence.cpp \ + test_ffs.cpp \ + test_file.cpp \ + test_file_progress.cpp \ + test_file_storage.cpp \ + test_flags.cpp \ + test_generate_peer_id.cpp \ + test_gzip.cpp \ + test_hash_picker.cpp \ + test_hasher.cpp \ + test_hasher512.cpp \ + test_heterogeneous_queue.cpp \ + test_http_connection.cpp \ + test_http_parser.cpp \ + test_identify_client.cpp \ + test_info_hash.cpp \ + test_io.cpp \ + test_ip_filter.cpp \ + test_ip_voter.cpp \ + test_listen_socket.cpp \ + test_lsd.cpp \ + test_magnet.cpp \ + test_merkle.cpp \ + test_merkle_tree.cpp \ + test_mmap.cpp \ + test_packet_buffer.cpp \ + test_part_file.cpp \ + test_pe_crypto.cpp \ + test_peer_classes.cpp \ + test_peer_list.cpp \ + test_peer_priority.cpp \ + test_piece_picker.cpp \ + test_primitives.cpp \ + test_priority.cpp \ + test_privacy.cpp \ + test_read_piece.cpp \ + test_read_resume.cpp \ + test_receive_buffer.cpp \ + test_recheck.cpp \ + test_remap_files.cpp \ + test_remove_torrent.cpp \ + test_resolve_links.cpp \ + test_resume.cpp \ + test_session.cpp \ + test_session_params.cpp \ + test_settings_pack.cpp \ + test_sha1_hash.cpp \ + test_similar_torrent.cpp \ + test_sliding_average.cpp \ + test_socket_io.cpp \ + test_span.cpp \ + test_ssl.cpp \ + test_stack_allocator.cpp \ + test_stat_cache.cpp \ + test_storage.cpp \ + test_store_buffer.cpp \ + test_string.cpp \ + test_tailqueue.cpp \ + test_threads.cpp \ + test_time.cpp \ + test_time_critical.cpp \ + test_timestamp_history.cpp \ + test_torrent.cpp \ + test_torrent_info.cpp \ + test_torrent_list.cpp \ + test_tracker.cpp \ + test_truncate.cpp \ + test_transfer.cpp \ + test_upnp.cpp \ + test_url_seed.cpp \ + test_utf8.cpp \ + test_utp.cpp \ + test_web_seed.cpp \ + test_web_seed_ban.cpp \ + test_web_seed_chunked.cpp \ + test_web_seed_http.cpp \ + test_web_seed_http_pw.cpp \ + test_web_seed_redirect.cpp \ + test_web_seed_socks4.cpp \ + test_web_seed_socks5.cpp \ + test_web_seed_socks5_no_peers.cpp \ + test_web_seed_socks5_pw.cpp \ + test_xml.cpp \ + \ + main.cpp \ + broadcast_socket.cpp \ + broadcast_socket.hpp \ + 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.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 + +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 \ + empty-files-1.torrent \ + empty-files-2.torrent \ + empty-files-3.torrent \ + empty-files-4.torrent \ + empty-files-5.torrent \ + hidden_parent_path.torrent \ + httpseed.torrent \ + invalid_file_size.torrent \ + invalid_filename.torrent \ + invalid_filename2.torrent \ + invalid_info.torrent \ + invalid_name.torrent \ + invalid_name2.torrent \ + invalid_name3.torrent \ + invalid_path_list.torrent \ + invalid_piece_len.torrent \ + invalid_pieces.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 \ + overlapping_symlinks.torrent \ + pad_file.torrent \ + pad_file_no_path.torrent \ + parent_path.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_single_file.torrent \ + url_seed_multi_space.torrent \ + url_seed_multi_space_nolist.torrent \ + whitespace_url.torrent \ + v2.torrent \ + v2_multipiece_file.torrent \ + v2_only.torrent \ + v2_invalid_filename.torrent \ + v2_mismatching_metadata.torrent \ + v2_no_power2_piece.torrent \ + v2_invalid_file.torrent \ + v2_deep_recursion.torrent \ + v2_non_multiple_piece_layer.torrent \ + v2_piece_layer_invalid_file_hash.torrent \ + v2_incomplete_piece_layer.torrent \ + v2_invalid_pad_file.torrent \ + v2_invalid_piece_layer.torrent \ + v2_invalid_piece_layer_size.torrent \ + v2_multiple_files.torrent \ + v2_bad_file_alignment.torrent \ + v2_unordered_files.torrent \ + v2_overlong_integer.torrent \ + v2_missing_file_root_invalid_symlink.torrent \ + v2_symlinks.torrent \ + v2_no_piece_layers.torrent \ + v2_large_file.torrent \ + v2_large_offset.torrent \ + v2_piece_size.torrent \ + v2_zero_root.torrent \ + v2_zero_root_small.torrent \ + v2_hybrid.torrent \ + v2_invalid_root_hash.torrent \ + zero.torrent \ + zero2.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 + +TEST_EXTRA = Jamfile \ + Jamfile \ + CMakeLists.txt \ + $(addprefix test_torrents/,${TEST_TORRENTS}) \ + $(addprefix mutable_test_torrents/,${MUTABLE_TEST_TORRENTS}) \ + root1.xml \ + root2.xml \ + root3.xml \ + 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 \ + socks.py \ + http_proxy.py \ + root1.xml \ + root2.xml \ + root3.xml + +dist: FORCE + (cd docs; make) + rm -rf libtorrent-rasterbar-${VERSION} libtorrent-rasterbar-${VERSION}.tar.gz + mkdir libtorrent-rasterbar-${VERSION} + rsync -R ${EXTRA_DIST} \ + $(addprefix src/,${SOURCES}) \ + $(addprefix src/kademlia/,${KADEMLIA_SOURCES}) \ + $(addprefix include/libtorrent/,${HEADERS}) \ + $(addprefix examples/,${EXAMPLE_FILES}) \ + $(addprefix tools/,${TOOLS_FILES}) \ + $(addprefix bindings/python/,${PYTHON_FILES}) \ + $(addprefix test/,${TEST_SOURCES}) \ + $(addprefix test/,${TEST_EXTRA}) \ + $(addprefix simulation/,${SIM_SOURCES}) \ + $(addprefix deps/try_signal/,${TRY_SIGNAL}) \ + $(addprefix deps/asio-gnutls/,${ASIO_GNUTLS}) \ + $(addprefix simulation/libsimulator/,${LIBSIM_EXTRA}) \ + $(addprefix simulation/libsimulator/test,${LIBSIM_TEST}) \ + $(addprefix simulation/libsimulator/include/simulator/,${LIBSIM_HEADERS}) \ + $(addprefix simulation/libsimulator/src/,${LIBSIM_SOURCES}) \ + $(addprefix src/ed25519/,$(ED25519_SOURCE)) \ + libtorrent-rasterbar-${VERSION} + tar -czf libtorrent-rasterbar-${VERSION}.tar.gz libtorrent-rasterbar-${VERSION} + +FORCE: + 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..1bd23dc --- /dev/null +++ b/README.rst @@ -0,0 +1,70 @@ +.. image:: docs/img/logo-color-text.png + +.. image:: https://github.com/arvidn/libtorrent/actions/workflows/windows.yml/badge.svg + :target: https://github.com/arvidn/libtorrent/actions/workflows/windows.yml + +.. image:: https://github.com/arvidn/libtorrent/actions/workflows/macos.yml/badge.svg + :target: https://github.com/arvidn/libtorrent/actions/workflows/macos.yml + +.. image:: https://github.com/arvidn/libtorrent/actions/workflows/linux.yml/badge.svg + :target: https://github.com/arvidn/libtorrent/actions/workflows/linux.yml + +.. image:: https://github.com/arvidn/libtorrent/actions/workflows/python.yml/badge.svg + :target: https://github.com/arvidn/libtorrent/actions/workflows/python.yml + +.. image:: https://ci.appveyor.com/api/projects/status/w7teauvub5813mew/branch/RC_2_0?svg=true + :target: https://ci.appveyor.com/project/arvidn/libtorrent/branch/RC_2_0 + +.. image:: https://api.cirrus-ci.com/github/arvidn/libtorrent.svg?branch=RC_2_0 + :target: https://cirrus-ci.com/github/arvidn/libtorrent + +.. 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=RC_2_0 + :target: https://codecov.io/github/arvidn/libtorrent?branch=RC_2_0&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://www.openhub.net/p/rasterbar-libtorrent/widgets/project_thin_badge.gif + :target: https://www.openhub.net/p/rasterbar-libtorrent + +.. 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 + +libtorrent package versions in linux distributions, on repology_. + +.. _repology: https://repology.org/project/libtorrent-rasterbar/versions + +.. __: docs/building.rst +.. __: docs/python_binding.rst + diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..097e158 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,107 @@ +version: "{build}" +branches: + only: + - master + - RC_2_0 + - RC_1_2 + - RC_1_1 +image: Visual Studio 2017 +clone_depth: 1 +environment: + matrix: + - variant: debug + compiler: gcc + model: 32 + crypto: openssl + ssl_lib: /usr/local/include + ssl_include: /usr/local/lib + lib: 1 + - cmake: 1 + - variant: release + compiler: msvc-14.1 + model: 64 + crypto: openssl + ssl_lib: c:\OpenSSL-v111-Win64\lib + ssl_include: c:\OpenSSL-v111-Win64\include + tests: 1 + +artifacts: + - path: bindings/python/dist/* + name: python-module + +install: + - git submodule update --init --recursive + - set ROOT_DIRECTORY=%CD% + - cd %ROOT_DIRECTORY% + - if not defined api ( set api="desktop" ) + - if not defined compiler ( set compiler="" ) + - if not defined crypto ( set crypto=built-in ) + - if not defined ssl_lib ( set ssl_lib=c:\ ) + - if not defined ssl_include ( set ssl_include=c:\ ) + - cd %ROOT_DIRECTORY% + - set BOOST_ROOT=c:\Libraries\boost_1_69_0 + - set BOOST_BUILD_PATH=%BOOST_ROOT%\tools\build + - echo %BOOST_ROOT% + - echo %BOOST_BUILD_PATH% + - set PATH=%PATH%;%BOOST_BUILD_PATH% + - ps: '"using msvc : 14.1 ;`nusing gcc ;`nusing python : 3.6 : c:\\Python36-x64 : c:\\Python36-x64\\include : c:\\Python36-x64\\libs ;`n" | Set-Content $env:HOMEDRIVE\$env:HOMEPATH\user-config.jam' + - type %HOMEDRIVE%%HOMEPATH%\user-config.jam + - cd %ROOT_DIRECTORY% + - set PATH=c:\msys64\mingw32\bin;%PATH% + - g++ --version + - set PATH=c:\Python36-x64;%PATH% + - set PYTHON_INTERPRETER=c:\Python36-x64\python.exe + - python --version + - echo %ROOT_DIRECTORY% + - cd %BOOST_BUILD_PATH% + - bootstrap.bat >nul + - cd %ROOT_DIRECTORY% + +build_script: + + # just the library + - cd %ROOT_DIRECTORY% + - if defined lib ( + b2.exe --hash openssl-lib=%ssl_lib% openssl-include=%ssl_include% warnings=all warnings-as-errors=on %compiler% address-model=%model% picker-debugging=on invariant-checks=full variant=%variant% link=shared crypto=%crypto% asserts=on export-extra=on windows-api=%api% windows-version=win10 cxxstd=14 + ) + + # test + - cd %ROOT_DIRECTORY%\test + - if defined tests ( + b2.exe --hash openssl-lib=%ssl_lib% openssl-include=%ssl_include% warnings=all warnings-as-errors=on %compiler% address-model=%model% picker-debugging=on invariant-checks=full variant=%variant% link=shared crypto=%crypto% asserts=on export-extra=on windows-api=%api% windows-version=win10 testing.execute=off + ) + + # python binding + - cd %ROOT_DIRECTORY%\bindings\python + # we use 64 bit python builds + # boost.python itself doesn't build warning free, so we can't build + # with warnings-as-errors + - if defined python ( + b2.exe --hash openssl-lib=%ssl_lib% openssl-include=%ssl_include% warnings=all %compiler% address-model=%model% picker-debugging=on invariant-checks=full variant=%variant% link=shared crypto=%crypto% asserts=on export-extra=on windows-api=%api% windows-version=win10 libtorrent-link=shared stage_module stage_dependencies + ) + - if defined python_dist ( + c:\Python36-x64\python.exe setup.py bdist --format=msi + ) + + # minimal support for cmake build + # we need to build the boost libraries we use with C++14 + # and stage it for cmake to pick up + - if defined cmake ( + cd %BOOST_ROOT% && + bjam cxxstd=14 release --with-python --with-system --layout=system address-model=64 link=shared stage && + cd %ROOT_DIRECTORY% && + mkdir build && + cd build && + cmake -DBOOST_LIBRARYDIR=%BOOST_ROOT%\stage\lib -DCMAKE_CXX_STANDARD=14 -Dbuild_tests=ON -Dbuild_examples=ON -Dbuild_tools=ON -Dpython-bindings=%python% -Dboost-python-module-name="python" -Dskip-python-runtime-test=true -DPython_ADDITIONAL_VERSIONS="2.7" -G "Visual Studio 15 2017" -A x64 .. && + cmake --build . --config Release --parallel %NUMBER_OF_PROCESSORS% -- -verbosity:minimal + ) + +test_script: + - cd %ROOT_DIRECTORY%\test + - if defined tests ( + appveyor-retry b2.exe -l500 --hash openssl-lib=%ssl_lib% openssl-include=%ssl_include% warnings=all warnings-as-errors=on %compiler% address-model=%model% picker-debugging=on invariant-checks=full variant=%variant% link=shared crypto=%crypto% asserts=on export-extra=on windows-api=%api% windows-version=win10 + ) + + - if defined cmake ( + appveyor-retry ctest + ) 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/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/c/Jamfile b/bindings/c/Jamfile new file mode 100644 index 0000000..1933a90 --- /dev/null +++ b/bindings/c/Jamfile @@ -0,0 +1,38 @@ +use-project /torrent : ../.. ; + +rule libtorrent_linking ( properties * ) +{ + local result ; + + if gcc in $(properties) && shared in $(properties) + { + result += on ; + } + +# if gcc in $(properties) || darwin in $(properties) +# { +# result += hidden ; +# } + + return $(result) ; +} + +lib torrentc + + : # sources + library.cpp + + : # requirements + @libtorrent_linking + /torrent//torrent/static + . + + : # default build + static + + : # usage-requirements + . +; + +exe simple_client : simple_client.c torrentc ; + diff --git a/bindings/c/library.cpp b/bindings/c/library.cpp new file mode 100644 index 0000000..e6cac82 --- /dev/null +++ b/bindings/c/library.cpp @@ -0,0 +1,606 @@ +/* + +Copyright (c) 2009, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_status.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/torrent_handle.hpp" + +#include +#include + +namespace +{ + std::vector handles; + + int find_handle(lt::torrent_handle h) + { + std::vector::const_iterator i + = std::find(handles.begin(), handles.end(), h); + if (i == handles.end()) return -1; + return i - handles.begin(); + } + + lt::torrent_handle get_handle(int i) + { + if (i < 0 || i >= int(handles.size())) return lt::torrent_handle(); + return handles[i]; + } + + int add_handle(lt::torrent_handle const& h) + { + std::vector::iterator i = std::find_if(handles.begin() + , handles.end() + , [](lt::torrent_handle const& h) { return !h.is_valid(); }); + if (i != handles.end()) + { + *i = h; + return i - handles.begin(); + } + + handles.push_back(h); + return handles.size() - 1; + } + + int set_int_value(void* dst, int* size, int val) + { + if (*size < sizeof(int)) return -2; + *((int*)dst) = val; + *size = sizeof(int); + return 0; + } + + void copy_proxy_setting(lt::proxy_settings* s, proxy_setting const* ps) + { + s->hostname.assign(ps->hostname); + s->port = ps->port; + s->username.assign(ps->username); + s->password.assign(ps->password); + s->type = (lt::proxy_settings::proxy_type)ps->type; + } +} + +extern "C" +{ + +TORRENT_EXPORT void* session_create(int tag, ...) +{ + using namespace lt; + + va_list lp; + va_start(lp, tag); + + fingerprint fing("LT", lt::version_major, lt::version_minor, lt::version_tiny, 0); + std::pair listen_range(-1, -1); + char const* listen_interface = "0.0.0.0"; + int flags = session::start_default_features | session::add_default_plugins; + int alert_mask = alert::error_notification; + + while (tag != TAG_END) + { + switch (tag) + { + case SES_FINGERPRINT: + { + char const* f = va_arg(lp, char const*); + fing.name[0] = f[0]; + fing.name[1] = f[1]; + break; + } + case SES_LISTENPORT: + listen_range.first = va_arg(lp, int); + break; + case SES_LISTENPORT_END: + listen_range.second = va_arg(lp, int); + break; + case SES_VERSION_MAJOR: + fing.major_version = va_arg(lp, int); + break; + case SES_VERSION_MINOR: + fing.minor_version = va_arg(lp, int); + break; + case SES_VERSION_TINY: + fing.revision_version = va_arg(lp, int); + break; + case SES_VERSION_TAG: + fing.tag_version = va_arg(lp, int); + break; + case SES_FLAGS: + flags = va_arg(lp, int); + break; + case SES_ALERT_MASK: + alert_mask = va_arg(lp, int); + break; + case SES_LISTEN_INTERFACE: + listen_interface = va_arg(lp, char const*); + break; + default: + // skip unknown tags + va_arg(lp, void*); + break; + } + + tag = va_arg(lp, int); + } + + if (listen_range.first != -1 && (listen_range.second == -1 + || listen_range.second < listen_range.first)) + listen_range.second = listen_range.first; + + return new (std::nothrow) session(fing, listen_range, listen_interface, flags, alert_mask); +} + +TORRENT_EXPORT void session_close(void* ses) +{ + delete (lt::session*)ses; +} + +TORRENT_EXPORT int session_add_torrent(void* ses, int tag, ...) +{ + using namespace lt; + + va_list lp; + va_start(lp, tag); + session* s = (session*)ses; + add_torrent_params params; + + char const* torrent_data = 0; + int torrent_size = 0; + + char const* resume_data = 0; + int resume_size = 0; + + char const* magnet_url = 0; + + error_code ec; + + while (tag != TAG_END) + { + switch (tag) + { + case TOR_FILENAME: + params.ti.reset(new (std::nothrow) torrent_info(va_arg(lp, char const*), ec)); + break; + case TOR_TORRENT: + torrent_data = va_arg(lp, char const*); + break; + case TOR_TORRENT_SIZE: + torrent_size = va_arg(lp, int); + break; + case TOR_INFOHASH: + params.ti.reset(new (std::nothrow) torrent_info(sha1_hash(va_arg(lp, char const*)))); + break; + case TOR_INFOHASH_HEX: + { + sha1_hash ih; + from_hex(va_arg(lp, char const*), 40, (char*)&ih[0]); + params.ti.reset(new (std::nothrow) torrent_info(ih)); + break; + } + case TOR_MAGNETLINK: + magnet_url = va_arg(lp, char const*); + break; + case TOR_TRACKER_URL: + params.tracker_url = va_arg(lp, char const*); + break; + case TOR_RESUME_DATA: + resume_data = va_arg(lp, char const*); + break; + case TOR_RESUME_DATA_SIZE: + resume_size = va_arg(lp, int); + break; + case TOR_SAVE_PATH: + params.save_path = va_arg(lp, char const*); + break; + case TOR_NAME: + params.name = va_arg(lp, char const*); + break; + case TOR_PAUSED: + params.paused = va_arg(lp, int) != 0; + break; + case TOR_AUTO_MANAGED: + params.auto_managed = va_arg(lp, int) != 0; + break; + case TOR_DUPLICATE_IS_ERROR: + params.duplicate_is_error = va_arg(lp, int) != 0; + break; + case TOR_USER_DATA: + params.userdata = va_arg(lp, void*); + break; + case TOR_SEED_MODE: + params.seed_mode = va_arg(lp, int) != 0; + break; + case TOR_OVERRIDE_RESUME_DATA: + params.override_resume_data = va_arg(lp, int) != 0; + break; + case TOR_STORAGE_MODE: + params.storage_mode = (lt::storage_mode_t)va_arg(lp, int); + break; + default: + // ignore unknown tags + va_arg(lp, void*); + break; + } + + tag = va_arg(lp, int); + } + + if (!params.ti && torrent_data && torrent_size) + params.ti.reset(new (std::nothrow) torrent_info(torrent_data, torrent_size)); + + std::vector rd; + if (resume_data && resume_size) + { + params.resume_data.assign(resume_data, resume_data + resume_size); + } + torrent_handle h; + if (!params.ti && magnet_url) + { + h = add_magnet_uri(*s, magnet_url, params, ec); + } + else + { + h = s->add_torrent(params, ec); + } + + if (!h.is_valid()) + { + return -1; + } + + int i = find_handle(h); + if (i == -1) i = add_handle(h); + + return i; +} + +TORRENT_EXPORT void session_remove_torrent(void* ses, int tor, int flags) +{ + using namespace lt; + torrent_handle h = get_handle(tor); + if (!h.is_valid()) return; + + session* s = (session*)ses; + s->remove_torrent(h, flags); +} + +TORRENT_EXPORT int session_pop_alert(void* ses, char* dest, int len, int* category) +{ + using namespace lt; + + session* s = (session*)ses; + + std::auto_ptr a = s->pop_alert(); + if (!a.get()) return -1; + + if (category) *category = a->category(); + strncpy(dest, a->message().c_str(), len - 1); + dest[len - 1] = 0; + + return 0; // for now +} + +TORRENT_EXPORT int session_set_settings(void* ses, int tag, ...) +{ + using namespace lt; + + session* s = (session*)ses; + + va_list lp; + va_start(lp, tag); + + while (tag != TAG_END) + { + switch (tag) + { + case SET_UPLOAD_RATE_LIMIT: + s->set_upload_rate_limit(va_arg(lp, int)); + break; + case SET_DOWNLOAD_RATE_LIMIT: + s->set_download_rate_limit(va_arg(lp, int)); + break; + case SET_LOCAL_UPLOAD_RATE_LIMIT: + s->set_local_upload_rate_limit(va_arg(lp, int)); + break; + case SET_LOCAL_DOWNLOAD_RATE_LIMIT: + s->set_local_download_rate_limit(va_arg(lp, int)); + break; + case SET_MAX_UPLOAD_SLOTS: + s->set_max_uploads(va_arg(lp, int)); + break; + case SET_MAX_CONNECTIONS: + s->set_max_connections(va_arg(lp, int)); + break; + case SET_HALF_OPEN_LIMIT: + s->set_max_half_open_connections(va_arg(lp, int)); + break; + case SET_PEER_PROXY: + { + lt::proxy_settings ps; + copy_proxy_setting(&ps, va_arg(lp, struct proxy_setting const*)); + s->set_peer_proxy(ps); + } + case SET_WEB_SEED_PROXY: + { + lt::proxy_settings ps; + copy_proxy_setting(&ps, va_arg(lp, struct proxy_setting const*)); + s->set_web_seed_proxy(ps); + } + case SET_TRACKER_PROXY: + { + lt::proxy_settings ps; + copy_proxy_setting(&ps, va_arg(lp, struct proxy_setting const*)); + s->set_tracker_proxy(ps); + } + case SET_ALERT_MASK: + { + s->set_alert_mask(va_arg(lp, int)); + } +#ifndef TORRENT_DISABLE_DHT + case SET_DHT_PROXY: + { + lt::proxy_settings ps; + copy_proxy_setting(&ps, va_arg(lp, struct proxy_setting const*)); + s->set_dht_proxy(ps); + } +#endif + case SET_PROXY: + { + lt::proxy_settings ps; + copy_proxy_setting(&ps, va_arg(lp, struct proxy_setting const*)); + s->set_peer_proxy(ps); + s->set_web_seed_proxy(ps); + s->set_tracker_proxy(ps); +#ifndef TORRENT_DISABLE_DHT + s->set_dht_proxy(ps); +#endif + } + default: + // ignore unknown tags + va_arg(lp, void*); + break; + } + + tag = va_arg(lp, int); + } + return 0; +} + +TORRENT_EXPORT int session_get_setting(void* ses, int tag, void* value, int* value_size) +{ + using namespace lt; + session* s = (session*)ses; + + switch (tag) + { + case SET_UPLOAD_RATE_LIMIT: + return set_int_value(value, value_size, s->upload_rate_limit()); + case SET_DOWNLOAD_RATE_LIMIT: + return set_int_value(value, value_size, s->download_rate_limit()); + case SET_LOCAL_UPLOAD_RATE_LIMIT: + return set_int_value(value, value_size, s->local_upload_rate_limit()); + case SET_LOCAL_DOWNLOAD_RATE_LIMIT: + return set_int_value(value, value_size, s->local_download_rate_limit()); + case SET_MAX_UPLOAD_SLOTS: + return set_int_value(value, value_size, s->max_uploads()); + case SET_MAX_CONNECTIONS: + return set_int_value(value, value_size, s->max_connections()); + case SET_HALF_OPEN_LIMIT: + return set_int_value(value, value_size, s->max_half_open_connections()); + default: + return -2; + } +} + +TORRENT_EXPORT int session_get_status(void* sesptr, struct session_status* s, int struct_size) +{ + lt::session* ses = (lt::session*)sesptr; + + lt::session_status ss = ses->status(); + if (struct_size != sizeof(session_status)) return -1; + + s->has_incoming_connections = ss.has_incoming_connections; + + s->upload_rate = ss.upload_rate; + s->download_rate = ss.download_rate; + s->total_download = ss.total_download; + s->total_upload = ss.total_upload; + + s->payload_upload_rate = ss.payload_upload_rate; + s->payload_download_rate = ss.payload_download_rate; + s->total_payload_download = ss.total_payload_download; + s->total_payload_upload = ss.total_payload_upload; + + s->ip_overhead_upload_rate = ss.ip_overhead_upload_rate; + s->ip_overhead_download_rate = ss.ip_overhead_download_rate; + s->total_ip_overhead_download = ss.total_ip_overhead_download; + s->total_ip_overhead_upload = ss.total_ip_overhead_upload; + + s->dht_upload_rate = ss.dht_upload_rate; + s->dht_download_rate = ss.dht_download_rate; + s->total_dht_download = ss.total_dht_download; + s->total_dht_upload = ss.total_dht_upload; + + s->tracker_upload_rate = ss.tracker_upload_rate; + s->tracker_download_rate = ss.tracker_download_rate; + s->total_tracker_download = ss.total_tracker_download; + s->total_tracker_upload = ss.total_tracker_upload; + + s->total_redundant_bytes = ss.total_redundant_bytes; + s->total_failed_bytes = ss.total_failed_bytes; + + s->num_peers = ss.num_peers; + s->num_unchoked = ss.num_unchoked; + s->allowed_upload_slots = ss.allowed_upload_slots; + + s->up_bandwidth_queue = ss.up_bandwidth_queue; + s->down_bandwidth_queue = ss.down_bandwidth_queue; + + s->up_bandwidth_bytes_queue = ss.up_bandwidth_bytes_queue; + s->down_bandwidth_bytes_queue = ss.down_bandwidth_bytes_queue; + + s->optimistic_unchoke_counter = ss.optimistic_unchoke_counter; + s->unchoke_counter = ss.unchoke_counter; + + s->dht_nodes = ss.dht_nodes; + s->dht_node_cache = ss.dht_node_cache; + s->dht_torrents = ss.dht_torrents; + s->dht_global_nodes = ss.dht_global_nodes; + return 0; +} + +TORRENT_EXPORT int torrent_get_status(int tor, torrent_status* s, int struct_size) +{ + lt::torrent_handle h = get_handle(tor); + if (!h.is_valid()) return -1; + + lt::torrent_status ts = h.status(); + + if (struct_size != sizeof(torrent_status)) return -1; + + s->state = (state_t)ts.state; + s->paused = ts.paused; + s->progress = ts.progress; + strncpy(s->error, ts.error.c_str(), 1025); + s->next_announce = lt::total_seconds(ts.next_announce); + s->announce_interval = lt::total_seconds(ts.announce_interval); + strncpy(s->current_tracker, ts.current_tracker.c_str(), 512); + s->total_download = ts.total_download = ts.total_download = ts.total_download; + s->total_upload = ts.total_upload = ts.total_upload = ts.total_upload; + s->total_payload_download = ts.total_payload_download; + s->total_payload_upload = ts.total_payload_upload; + s->total_failed_bytes = ts.total_failed_bytes; + s->total_redundant_bytes = ts.total_redundant_bytes; + s->download_rate = ts.download_rate; + s->upload_rate = ts.upload_rate; + s->download_payload_rate = ts.download_payload_rate; + s->upload_payload_rate = ts.upload_payload_rate; + s->num_seeds = ts.num_seeds; + s->num_peers = ts.num_peers; + s->num_complete = ts.num_complete; + s->num_incomplete = ts.num_incomplete; + s->list_seeds = ts.list_seeds; + s->list_peers = ts.list_peers; + s->connect_candidates = ts.connect_candidates; + s->num_pieces = ts.num_pieces; + s->total_done = ts.total_done; + s->total_wanted_done = ts.total_wanted_done; + s->total_wanted = ts.total_wanted; + s->distributed_copies = ts.distributed_copies; + s->block_size = ts.block_size; + s->num_uploads = ts.num_uploads; + s->num_connections = ts.num_connections; + s->uploads_limit = ts.uploads_limit; + s->connections_limit = ts.connections_limit; +// s->storage_mode = (storage_mode_t)ts.storage_mode; + s->up_bandwidth_queue = ts.up_bandwidth_queue; + s->down_bandwidth_queue = ts.down_bandwidth_queue; + s->all_time_upload = ts.all_time_upload; + s->all_time_download = ts.all_time_download; + s->active_time = ts.active_time; + s->seeding_time = ts.seeding_time; + s->seed_rank = ts.seed_rank; + s->last_scrape = ts.last_scrape; + s->has_incoming = ts.has_incoming; + s->seed_mode = ts.seed_mode; + return 0; +} + +TORRENT_EXPORT int torrent_set_settings(int tor, int tag, ...) +{ + using namespace lt; + torrent_handle h = get_handle(tor); + if (!h.is_valid()) return -1; + + va_list lp; + va_start(lp, tag); + + while (tag != TAG_END) + { + switch (tag) + { + case SET_UPLOAD_RATE_LIMIT: + h.set_upload_limit(va_arg(lp, int)); + break; + case SET_DOWNLOAD_RATE_LIMIT: + h.set_download_limit(va_arg(lp, int)); + break; + case SET_MAX_UPLOAD_SLOTS: + h.set_max_uploads(va_arg(lp, int)); + break; + case SET_MAX_CONNECTIONS: + h.set_max_connections(va_arg(lp, int)); + break; + case SET_SEQUENTIAL_DOWNLOAD: + h.set_sequential_download(va_arg(lp, int) != 0); + break; + case SET_SUPER_SEEDING: + h.super_seeding(va_arg(lp, int) != 0); + break; + default: + // ignore unknown tags + va_arg(lp, void*); + break; + } + + tag = va_arg(lp, int); + } + return 0; +} + +TORRENT_EXPORT int torrent_get_setting(int tor, int tag, void* value, int* value_size) +{ + using namespace lt; + torrent_handle h = get_handle(tor); + if (!h.is_valid()) return -1; + + switch (tag) + { + case SET_UPLOAD_RATE_LIMIT: + return set_int_value(value, value_size, h.upload_limit()); + case SET_DOWNLOAD_RATE_LIMIT: + return set_int_value(value, value_size, h.download_limit()); + case SET_MAX_UPLOAD_SLOTS: + return set_int_value(value, value_size, h.max_uploads()); + case SET_MAX_CONNECTIONS: + return set_int_value(value, value_size, h.max_connections()); + case SET_SEQUENTIAL_DOWNLOAD: + return set_int_value(value, value_size, h.is_sequential_download()); + case SET_SUPER_SEEDING: + return set_int_value(value, value_size, h.super_seeding()); + default: + return -2; + } +} + +} // extern "C" + diff --git a/bindings/c/libtorrent.h b/bindings/c/libtorrent.h new file mode 100644 index 0000000..f761cae --- /dev/null +++ b/bindings/c/libtorrent.h @@ -0,0 +1,296 @@ +/* + +Copyright (c) 2009, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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_H +#define LIBTORRENT_H + +enum tags +{ + TAG_END = 0, + + SES_FINGERPRINT, // char const*, 2 character string + SES_LISTENPORT, // int + SES_LISTENPORT_END, // int + SES_VERSION_MAJOR, // int + SES_VERSION_MINOR, // int + SES_VERSION_TINY, // int + SES_VERSION_TAG, // int + SES_FLAGS, // int + SES_ALERT_MASK, // int + SES_LISTEN_INTERFACE, // char const* + + // === add_torrent tags === + + // identifying the torrent to add + TOR_FILENAME = 0x100, // char const* + TOR_TORRENT, // char const*, specify size of buffer with TOR_TORRENT_SIZE + TOR_TORRENT_SIZE, // int + TOR_INFOHASH, // char const*, must point to a 20 byte array + TOR_INFOHASH_HEX, // char const*, must point to a 40 byte string + TOR_MAGNETLINK, // char const*, url + + TOR_TRACKER_URL, // char const* + TOR_RESUME_DATA, // char const* + TOR_RESUME_DATA_SIZE, // int + TOR_SAVE_PATH, // char const* + TOR_NAME, // char const* + TOR_PAUSED, // int + TOR_AUTO_MANAGED, // int + TOR_DUPLICATE_IS_ERROR, // int + TOR_USER_DATA, //void* + TOR_SEED_MODE, // int + TOR_OVERRIDE_RESUME_DATA, // int + TOR_STORAGE_MODE, // int + + SET_UPLOAD_RATE_LIMIT = 0x200, // int + SET_DOWNLOAD_RATE_LIMIT, // int + SET_LOCAL_UPLOAD_RATE_LIMIT, // int + SET_LOCAL_DOWNLOAD_RATE_LIMIT, // int + SET_MAX_UPLOAD_SLOTS, // int + SET_MAX_CONNECTIONS, // int + SET_SEQUENTIAL_DOWNLOAD, // int, torrent only + SET_SUPER_SEEDING, // int, torrent only + SET_HALF_OPEN_LIMIT, // int, session only + SET_PEER_PROXY, // proxy_setting const*, session_only + SET_WEB_SEED_PROXY, // proxy_setting const*, session_only + SET_TRACKER_PROXY, // proxy_setting const*, session_only + SET_DHT_PROXY, // proxy_setting const*, session_only + SET_PROXY, // proxy_setting const*, session_only + SET_ALERT_MASK, // int, session_only +}; + +struct proxy_setting +{ + char hostname[256]; + int port; + + char username[256]; + char password[256]; + + int type; +}; + +enum category_t +{ + cat_error = 0x1, + cat_peer = 0x2, + cat_port_mapping = 0x4, + cat_storage = 0x8, + cat_tracker = 0x10, + cat_debug = 0x20, + cat_status = 0x40, + cat_progress = 0x80, + cat_ip_block = 0x100, + cat_performance_warning = 0x200, + cat_dht = 0x400, + + cat_all_categories = 0xffffffff +}; + +enum proxy_type_t +{ + proxy_none, + proxy_socks4, + proxy_socks5, + proxy_socks5_pw, + proxy_http, + proxy_http_pw +}; + +enum storage_mode_t +{ + storage_mode_allocate = 0, + storage_mode_sparse +}; + +enum state_t +{ + queued_for_checking, + checking_files, + downloading_metadata, + downloading, + finished, + seeding, + allocating, + checking_resume_data +}; + +struct torrent_status +{ + enum state_t state; + int paused; + float progress; + char error[1024]; + int next_announce; + int announce_interval; + char current_tracker[512]; + long long total_download; + long long total_upload; + long long total_payload_download; + long long total_payload_upload; + long long total_failed_bytes; + long long total_redundant_bytes; + float download_rate; + float upload_rate; + float download_payload_rate; + float upload_payload_rate; + int num_seeds; + int num_peers; + int num_complete; + int num_incomplete; + int list_seeds; + int list_peers; + int connect_candidates; + + // what to do? +// bitfield pieces; + + int num_pieces; + long long total_done; + long long total_wanted_done; + long long total_wanted; + float distributed_copies; + int block_size; + int num_uploads; + int num_connections; + int uploads_limit; + int connections_limit; +// enum storage_mode_t storage_mode; + int up_bandwidth_queue; + int down_bandwidth_queue; + long long all_time_upload; + long long all_time_download; + int active_time; + int seeding_time; + int seed_rank; + int last_scrape; + int has_incoming; + int seed_mode; +}; + +struct session_status +{ + int has_incoming_connections; + + float upload_rate; + float download_rate; + long long total_download; + long long total_upload; + + float payload_upload_rate; + float payload_download_rate; + long long total_payload_download; + long long total_payload_upload; + + float ip_overhead_upload_rate; + float ip_overhead_download_rate; + long long total_ip_overhead_download; + long long total_ip_overhead_upload; + + float dht_upload_rate; + float dht_download_rate; + long long total_dht_download; + long long total_dht_upload; + + float tracker_upload_rate; + float tracker_download_rate; + long long total_tracker_download; + long long total_tracker_upload; + + long long total_redundant_bytes; + long long total_failed_bytes; + + int num_peers; + int num_unchoked; + int allowed_upload_slots; + + int up_bandwidth_queue; + int down_bandwidth_queue; + + int up_bandwidth_bytes_queue; + int down_bandwidth_bytes_queue; + + int optimistic_unchoke_counter; + int unchoke_counter; + + int dht_nodes; + int dht_node_cache; + int dht_torrents; + long long dht_global_nodes; +// std::vector active_requests; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +// the functions whose signature ends with: +// , int first_tag, ...); +// takes a tag list. The tag list is a series +// of tag-value pairs. The tags are constants +// identifying which property the value controls. +// The type of the value varies between tags. +// The enumeration above specifies which type +// it expects. All tag lists must always be +// terminated by TAG_END. + +// use SES_* tags in tag list +void* session_create(int first_tag, ...); +void session_close(void* ses); + +// use TOR_* tags in tag list +int session_add_torrent(void* ses, int first_tag, ...); +void session_remove_torrent(void* ses, int tor, int flags); + +// return < 0 if there are no alerts. Otherwise returns the +// type of alert that was returned +int session_pop_alert(void* ses, char* dest, int len, int* category); + +int session_get_status(void* ses, struct session_status* s, int struct_size); + +// use SET_* tags in tag list +int session_set_settings(void* ses, int first_tag, ...); +int session_get_setting(void* ses, int tag, void* value, int* value_size); + +int torrent_get_status(int tor, struct torrent_status* s, int struct_size); + +// use SET_* tags in tag list +int torrent_set_settings(int tor, int first_tag, ...); +int torrent_get_setting(int tor, int tag, void* value, int* value_size); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/bindings/c/simple_client.c b/bindings/c/simple_client.c new file mode 100644 index 0000000..3c1d7a2 --- /dev/null +++ b/bindings/c/simple_client.c @@ -0,0 +1,123 @@ +/* + +Copyright (c) 2009, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 + +int quit = 0; + +void stop(int signal) +{ + quit = 1; +} + +int main(int argc, char* argv[]) +{ + if (argc != 2) + { + fprintf(stderr, "usage: ./simple_client torrent-file\n"); + return 1; + } + + int ret = 0; + void* ses = session_create( + SES_LISTENPORT, 6881, + SES_LISTENPORT_END, 6889, + SES_ALERT_MASK, ~(cat_progress | cat_port_mapping | cat_debug | cat_performance_warning | cat_peer), + TAG_END); + + int t = session_add_torrent(ses, + TOR_FILENAME, argv[1], + TOR_SAVE_PATH, "./", + TAG_END); + + if (t < 0) + { + fprintf(stderr, "Failed to add torrent\n"); + ret = 1; + goto exit; + } + + struct torrent_status st; + + printf("press ctrl-C to stop\n"); + + signal(SIGINT, &stop); + signal(SIGABRT, &stop); + signal(SIGQUIT, &stop); + + while (quit == 0) + { + char const* message = ""; + + char const* state[] = {"queued", "checking", "downloading metadata" + , "downloading", "finished", "seeding", "allocating" + , "checking_resume_data"}; + + if (torrent_get_status(t, &st, sizeof(st)) < 0) break; + printf("\r%3.f%% %d kB (%5.f kB/s) up: %d kB (%5.f kB/s) peers: %d '%s' %s " + , (double)st.progress * 100. + , (int)(st.total_payload_download / 1000) + , (double)st.download_payload_rate / 1000. + , (int)(st.total_payload_upload / 1000) + , (double)st.upload_payload_rate / 1000. + , st.num_peers + , state[st.state] + , message); + + + char msg[400]; + while (session_pop_alert(ses, msg, sizeof(msg), 0) >= 0) + { + printf("%s\n", msg); + } + + if (strlen(st.error) > 0) + { + fprintf(stderr, "\nERROR: %s\n", st.error); + break; + } + + fflush(stdout); + usleep(1000000); + } + printf("\nclosing\n"); + +exit: + + session_close(ses); + return ret; +} + diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt new file mode 100644 index 0000000..bb4fd3b --- /dev/null +++ b/bindings/python/CMakeLists.txt @@ -0,0 +1,127 @@ +cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR) # Configurable policies: <= CMP0102 + +# 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 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 +# See https://devguide.python.org/#status-of-python-branches for supported python versions +function(_get_compatible_python_versions _ret) + if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 20) + list(APPEND _tmp 3.6 3.7 3.8 3.9) + 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(Python3 COMPONENTS Interpreter Development 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: ${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}") + if (NOT "${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}" IN_LIST Python_ADDITIONAL_VERSIONS) + message(FATAL_ERROR "Incompatible Python and C runtime: MSVC ${CMAKE_CXX_COMPILER_VERSION} and Python ${Python3_VERSION}") + endif() +endif() + +set(boost-python-module-name "python${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}" CACHE STRING "Boost::python module name, e.g. 'python-3.6'") + +find_package(Boost REQUIRED COMPONENTS ${boost-python-module-name}) + +Python3_add_library(python-libtorrent MODULE WITH_SOABI + src/alert.cpp + src/converters.cpp + src/create_torrent.cpp + src/datetime.cpp + src/entry.cpp + src/error_code.cpp + src/fingerprint.cpp + src/info_hash.cpp + src/ip_filter.cpp + src/magnet_uri.cpp + src/module.cpp + src/peer_info.cpp + src/session.cpp + src/session_settings.cpp + src/sha1_hash.cpp + src/sha256_hash.cpp + src/string.cpp + src/torrent_handle.cpp + src/torrent_info.cpp + src/torrent_status.cpp + src/utility.cpp + src/version.cpp +) + +set_target_properties(python-libtorrent + PROPERTIES + OUTPUT_NAME libtorrent +) + +if (MSVC) + target_compile_options(python-libtorrent PRIVATE /bigobj) +endif() + +target_link_libraries(python-libtorrent + PRIVATE + torrent-rasterbar + "Boost::${boost-python-module-name}" +) + +# 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() + +if (python-install-system-dir) + set(_PYTHON3_SITE_ARCH "${Python3_SITEARCH}") +else() + execute_process( + COMMAND "${Python3_EXECUTABLE}" -c [=[ +import distutils.sysconfig +print(distutils.sysconfig.get_python_lib(prefix='', plat_specific=True)) +]=] + OUTPUT_VARIABLE _PYTHON3_SITE_ARCH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) +endif() + +message(STATUS "Python 3 site packages: ${_PYTHON3_SITE_ARCH}") +message(STATUS "Python 3 extension suffix: ${Python3_SOABI}") + +install(TARGETS python-libtorrent DESTINATION "${_PYTHON3_SITE_ARCH}") + +if (python-egg-info) + 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 ${Python3_EXECUTABLE} ${SETUP_PY} egg_info + COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT} + DEPENDS ${DEPS} + ) + + add_custom_target(python_bindings ALL DEPENDS ${OUTPUT}) + + install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/libtorrent.egg-info" DESTINATION "${_PYTHON3_SITE_ARCH}") +endif() diff --git a/bindings/python/Jamfile b/bindings/python/Jamfile new file mode 100644 index 0000000..6a4e461 --- /dev/null +++ b/bindings/python/Jamfile @@ -0,0 +1,362 @@ +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 ] ; + +CXXFLAGS = [ modules.peek : CXXFLAGS ] ; +LDFLAGS = [ modules.peek : LDFLAGS ] ; + +ECHO "CXXFLAGS =" $(CXXFLAGS) ; +ECHO "LDFLAGS =" $(LDFLAGS) ; + +# this is used to make bjam use the same version of python which is executing setup.py + +feature libtorrent-link : shared static prebuilt : composite propagated ; + +feature libtorrent-python-pic : off on : composite propagated link-incompatible ; +feature.compose on : -fPIC ; + +# when invoking the install_module target, this feature can be specified to +# install the python module to a specific directory +feature python-install-path : : free path ; + +# when not specifying a custom install path, this controls whether to install +# the python module in the system directory or user-specifc directory +feature python-install-scope : user system : ; + +# 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 + ; + + import version ; + + rule boost_python_version ( name : type ? : properties * ) + { + # examples of names for the boost_python library: + # ubuntu bionic (1.65.1): libboost_python3-py36.so.1.65.1 + # ubuntu bionic (1.65): libboost_python-py36.so + # libboost_python3-py36.so + # libboost_python3.so + # ubuntu bionic (1.62.0): libboost_python-py36.so.1.62.0 + # ubuntu focal (1.67.0): libboost_python38.so.1.67.0 + # ubuntu focal (1.71.0): libboost_python38.so + # ubuntu groovy (1.71.0): libboost_python38.so + # ubuntu hirsute(1.71.0): libboost_python39.so + # debian buster (1.67.0): libboost_python37.so + # libboost_python3.so + # libboost_python3-py37.so + # debian sid (1.74.0): libboost_python39.so + # debian sid (1.71.0): libboost_python39.so + # debian bullseye (1.71): libboost_python39.so + # devian buster-backports (1.71): libboost_python37.so + # debian buster (1.67): libboost_python37.so.1.67.0 + # debian stretch (1.62.0): libboost_python-py35.so.1.62.0 + # debian stretch (1.62): libboost_python-py35.so + # debian jessie (1.55.0): libboost_python-py34.so.1.55.0 + # boost Jamfile: libboost_python38.so.1.73.0 + + local py-version-str = [ $(properties).get ] ; + local py-version = "" ; + local infix = "" ; + if $(py-version-str) { + py-version = [ SPLIT_BY_CHARACTERS $(py-version-str) : "." ] ; + + if [ version.version-less $(py-version) : 3 7 ] && [ $(properties).get ] = linux + { + infix = "-py" ; + } + } + local boost-python-lib = "boost_python" $(infix) $(py-version) ; + + if $(type) in SEARCHED_LIB + { + return $(boost-python-lib:J) ; + } + return ; + } + + lib boost_python : : @boost_python_version : : $(boost-include-path) ; + alias boost_python3 : boost_python ; +} + +lib prebuilt_libtorrent : : torrent-rasterbar : : ../../include ; +lib prebuilt_libtorrent : : windows torrent : : ../../include ; + +rule libtorrent_linking ( properties * ) +{ + local result ; + + # allow larger .obj files (with more sections) + if msvc in $(properties) || intel-win in $(properties) + { + # allow larger .obj files (with more sections) + result += /bigobj ; + } + + if gcc in $(properties) && windows in $(properties) + { + # allow larger .obj files (with more sections) + result += -Wa,-mbig-obj ; + } + + 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) + { + # hide non-external symbols + result += -fvisibility=hidden ; + result += -fvisibility-inlines-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" ; + } + + local BOOST_VERSION_TAG = [ modules.peek boostcpp : BOOST_VERSION_TAG ] ; + if static in $(properties) && $(BOOST_VERSION_TAG) < 1_74 && linux in $(properties) + { + ECHO "WARNING: you cannot link statically against boost-python on linux before version 1.74.0, 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) && $(BOOST_VERSION_TAG) < 1_74 ) + { + result += $(boost_python_lib)/shared/off ; + } + else + { + result += $(boost_python_lib)/static/off ; + } + + if shared in $(properties) + { + result += /torrent//torrent/shared ; + } + else if static in $(properties) + { + result += /torrent//torrent/static ; + } + else + { + result += prebuilt_libtorrent ; + } + + 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/sha256_hash.cpp + src/info_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 + "$(CXXFLAGS:J= )" + "$(LDFLAGS:J= )" + # C4268: 'identifier' : 'const' static/global data initialized + # with compiler generated default constructor fills the object with zeros + msvc:/wd4268 + : # default-build + all + 14 + : # usage-requirements + false + ; + +rule python-install-dir ( properties * ) +{ + local install-dir = [ feature.get-values python-install-path : $(properties) ] ; + if ( $(install-dir) != "" ) + { + # if the user has provided an install location, use that one + return $(install-dir) ; + } + + local python-interpreter = [ feature.get-values python.interpreter : $(properties) ] ; + if ( $(python-interpreter) = "" ) + { + return . ; + } + + # sys.path are defined differently between python2 and python3 + + local python-path ; + if system in $(properties) + { + python-path = [ SHELL "$(python-interpreter) -c \"import distutils.sysconfig; import sys; sys.stdout.write(distutils.sysconfig.get_python_lib())\"" ] ; + } + else + { + python-path = [ SHELL "$(python-interpreter) -c \"import site; import sys; sys.stdout.write(site.USER_SITE)\"" ] ; + } + + if $(python-path) = "" + { + return . ; + } + + ECHO "python install directory:" $(python-path) ; + return $(python-path) ; +} + +install install_module + : libtorrent + : @python-install-dir + PYTHON_EXTENSION + ; + +explicit install_module ; + +install stage_module + : libtorrent + : . + PYTHON_EXTENSION + : 14 + ; + +install stage_dependencies + : /torrent//torrent + boost_python + boost_python3 + : dependencies + on + SHARED_LIB + : 14 + ; + +explicit stage_module ; +explicit stage_dependencies ; + diff --git a/bindings/python/client.py b/bindings/python/client.py new file mode 100755 index 0000000..300572f --- /dev/null +++ b/bindings/python/client.py @@ -0,0 +1,381 @@ +#!/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: + ti = lt.torrent_info(filename) + resume_file = os.path.join(options.save_path, ti.name() + '.fastresume') + try: + atp = lt.read_resume_data(open(resume_file, 'rb').read()) + except Exception as e: + print('failed to open resume file "%s": %s' % (resume_file, e)) + atp.ti = ti + + 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', + '', '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_hashes + 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 idx, p in enumerate(fp): + out += progress_bar(p / float(ti.files().file_size(idx)), 20) + out += ' ' + ti.files().file_path(idx) + '\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/dummy_data.py b/bindings/python/dummy_data.py new file mode 100644 index 0000000..9803163 --- /dev/null +++ b/bindings/python/dummy_data.py @@ -0,0 +1,34 @@ +import libtorrent as lt + +import hashlib +import random + +PIECE_LENGTH = 16384 +NAME = b"test.txt" +LEN = PIECE_LENGTH * 9 + 1000 +# Use 7-bit data so we can test piece data as either bytes or str +DATA = bytes(random.getrandbits(7) for _ in range(LEN)) +PIECES = [DATA[i:i + PIECE_LENGTH] for i in range(0, LEN, PIECE_LENGTH)] + +INFO_DICT = { + b"name": NAME, + b"piece length": PIECE_LENGTH, + b"length": len(DATA), + b"pieces": b"".join(hashlib.sha1(p).digest() for p in PIECES), + } + +DICT = { + b"info": INFO_DICT, +} + + +def get_infohash_bytes(): + return hashlib.sha1(lt.bencode(INFO_DICT)).digest() + + +def get_infohash(): + return get_infohash_bytes().hex() + + +def get_sha1_hash(): + return lt.sha1_hash(get_infohash_bytes()) 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..66dd495 --- /dev/null +++ b/bindings/python/setup.py @@ -0,0 +1,463 @@ +#!/usr/bin/env python3 + +from distutils import log +import distutils.debug +import distutils.sysconfig +import distutils.util +import os +import pathlib +import sys +import sysconfig +import tempfile +import subprocess +import contextlib +import warnings +import re +import shlex + +import setuptools +import setuptools.command.build_ext as _build_ext_lib + + +def b2_bool(value): + if value: + return "on" + return "off" + + +# Frustratingly, the "bdist_*" unconditionally (re-)run "build" without +# args, even ignoring "build_*" earlier on the same command line. This +# means "build_*" must be a no-op if some build output exists, even if that +# output might have been generated with different args (like +# "--define=FOO"). b2 does not know how to be "naively idempotent" like +# this; it will only generate outputs that exactly match the build request. +# +# It doesn't work to short-circuit initialize_options() / finalize_options(), +# as this doesn't play well with the way options are externally manipulated by +# distutils. +# +# It DOES work to short-circuit Distribution.reinitialize_command(), so we do +# that here. + + +class B2Distribution(setuptools.Distribution): + def reinitialize_command(self, command, reinit_subcommands=0): + if command == "build_ext": + return self.get_command_obj("build_ext") + return super().reinitialize_command( + command, reinit_subcommands=reinit_subcommands + ) + + +# Various setuptools logic expects us to provide Extension instances for each +# extension in the distro. +class StubExtension(setuptools.Extension): + def __init__(self, name): + # An empty sources list ensures the base build_ext command won't build + # anything + super().__init__(name, sources=[]) + + +def b2_escape(value): + value = value.replace("\\", "\\\\") + value = value.replace('"', '\\"') + return f'"{value}"' + + +def write_b2_python_config(config): + write = config.write + # b2 keys python environments by X.Y version, breaking ties by matching + # a property list, called the "condition" of the environment. To ensure + # b2 always picks the environment we define here, we define a special + # feature for the condition and include that in the build request. + + # Note that we might try to reuse a property we know will be set, like + # TORRENT_FOO. But python.jam actually modifies the build request + # in this case, so that TORRENT_FOO becomes something like + # TORRENT_FOO,3.7,linux:... which causes chaos. + # We should always define a custom feature for the condition. + write("import feature ;\n") + write("feature.feature libtorrent-python : on ;\n") + + # python.jam tries to determine correct include and library paths. Per + # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=691378 , include + # detection is broken, but debian's fix is also broken (invokes a global + # pythonX.Y instead of the passed interpreter) + paths = sysconfig.get_paths() + includes = [paths["include"], paths["platinclude"]] + + write("using python") + write(f" : {sysconfig.get_python_version()}") + write(f" : {b2_escape(sys.executable)}") + write(" : ") + write(" ".join(b2_escape(path) for path in includes)) + write(" :") # libraries + write(" : on") + + # Note that all else being equal, we'd like to exactly control the output + # filename, so distutils can find it. However: + # 1. We can only control part of the filename; the prefix is controlled by + # our Jamfile and the final suffix is controlled by python.jam. + # 2. Debian patched python.jam to disregard the configured ext_suffix + # anyway; they always override it with the same sysconfig var we use, + # found by invoking the executable. + + # So instead of applying an arbitrary name, we just try to guarantee that + # b2 produces a name that distutils would expect, on all platforms. In + # other words we apply debian's override everywhere, and hope no other + # overrides ever disagree with us. + + # Note that sysconfig and distutils.sysconfig disagree here, especially on + # windows. + ext_suffix = distutils.sysconfig.get_config_var("EXT_SUFFIX") + + # python.jam appends the platform-specific final suffix on its own. I can't + # find a consistent value from sysconfig or distutils.sysconfig for this. + for plat_suffix in (".pyd", ".dll", ".so", ".sl"): + if ext_suffix.endswith(plat_suffix): + ext_suffix = ext_suffix[: -len(plat_suffix)] + break + write(f" : {b2_escape(ext_suffix)}") + write(" ;\n") + + +BuildExtBase = _build_ext_lib.build_ext + + +class LibtorrentBuildExt(BuildExtBase): + + CONFIG_MODE_DISTUTILS = "distutils" + CONFIG_MODE_B2 = "b2" + CONFIG_MODES = (CONFIG_MODE_DISTUTILS, CONFIG_MODE_B2) + + user_options = BuildExtBase.user_options + [ + ( + "config-mode=", + None, + "'b2' or 'distutils' (default). " + "In b2 mode, setup.py will just invoke b2 using --b2-args. " + "It will not attempt to auto-configure b2 or override any " + "args. " + "In distutils mode, setup.py will attempt to configure " + "and invoke b2 to match the expectations and behavior of " + "distutils (libtorrent will be built against the invoking " + "python, etc; note not all behaviors are currently supported). " + "The feature set will match the version found on pypi. " + "You can selectively override the auto-configuration in " + "this mode with --no-autoconf or --b2-args. For example " + "--b2-args=python=x.y or --no-autoconf=python will prevent " + "python from being auto-configured. " + "Note that --b2-args doesn't currently understand implicit features. " + "Be sure to include their names, e.g. --b2-args=variant=debug", + ), + ( + "b2-args=", + None, + "The full argument string to pass to b2. This is parsed with shlex, to " + "support arguments with spaces. For example: --b2-args 'variant=debug " + '"my-feature=a value with spaces"\'', + ), + ( + "no-autoconf=", + None, + "Space-separated list of b2 arguments that should not be " + "auto-configured in distutils mode.", + ), + ( + "libtorrent-link=", + None, + "(DEPRECATED; use --b2-args=libtorrent-link=...) ", + ), + ( + "boost-link=", + None, + "(DEPRECATED; use --b2-args=boost-link=...) " + ), + ("toolset=", None, "(DEPRECATED; use --b2-args=toolset=...) b2 toolset"), + ( + "pic", + None, + "(DEPRECATED; use --b2-args=libtorrent-python-pic=on) " + "whether to compile with -fPIC", + ), + ( + "optimization=", + None, + "(DEPRECATED; use --b2-args=optimization=...) " "b2 optimization mode", + ), + ( + "hash", + None, + "(DEPRECATED; use --b2-args=--hash) " + "use a property hash for the build directory, rather than " + "property subdirectories", + ), + ( + "cxxstd=", + None, + "(DEPRECATED; use --b2-args=cxxstd=...) " + "boost cxxstd value (14, 17, 20, etc.)", + ), + ] + + boolean_options = BuildExtBase.boolean_options + ["pic", "hash"] + + def initialize_options(self): + + self.config_mode = self.CONFIG_MODE_DISTUTILS + self.b2_args = "" + self.no_autoconf = "" + + self.cxxflags = None + self.linkflags = None + + # TODO: this is for backwards compatibility + # loading these files will be removed in libtorrent-2.0 + try: + with open('compile_flags') as f: + opts = f.read() + if '-std=c++' in opts: + self.cxxflags = ['-std=c++' + opts.split('-std=c++')[-1].split()[0]] + except OSError: + pass + + # TODO: this is for backwards compatibility + # loading these files will be removed in libtorrent-2.0 + try: + with open('link_flags') as f: + opts = f.read().split(' ') + opts = [x for x in opts if x.startswith('-L')] + if len(opts): + self.linkflags = opts + except OSError: + pass + + self.toolset = None + self.libtorrent_link = None + self.boost_link = None + self.pic = None + self.optimization = None + self.hash = None + self.cxxstd = None + + self._b2_args_split = [] + self._b2_args_configured = set() + + return super().initialize_options() + + def finalize_options(self): + super().finalize_options() + + if self.config_mode not in self.CONFIG_MODES: + raise distutils.errors.DistutilsOptionError( + f"--config-mode must be one of {self.CONFIG_MODES}" + ) + + # shlex the args here to warn early on bad config + self._b2_args_split = shlex.split(self.b2_args or "") + self._b2_args_configured.update(shlex.split(self.no_autoconf or "")) + + # In b2's arg system only single-character args can consume the next + # arg, but it may also be concatenated. So we may have "-x", + # "-x value", or "-xvalue". All --long args which take a value must + # appear as "--long=value" + i = 0 + while i < len(self._b2_args_split): + arg = self._b2_args_split[i] + m = re.match(r"(-[dfjlmopst])(.*)", arg) + if m: + name = m.group(1) + # An arg that takes a value but wasn't concatenated. Treat the + # next option as the value + if not m.group(2): + i += 1 + else: + name = arg.split("=", 1)[0] + self._b2_args_configured.add(name) + i += 1 + + # Add deprecated args + if self.libtorrent_link: + warnings.warn( + "--libtorrent-link is deprecated; use --b2-args=libtorrent-link=..." + ) + self._maybe_add_arg(f"libtorrent-link={self.libtorrent_link}") + self._b2_args_configured.add("libtorrent-link") + if self.boost_link: + warnings.warn("--boost-link is deprecated; use --b2-args=boost-link=...") + self._maybe_add_arg(f"boost-link={self.boost_link}") + self._b2_args_configured.add("boost-link") + if self.toolset: + warnings.warn("--toolset is deprecated; use --b2-args=toolset=...") + self._maybe_add_arg(f"toolset={self.toolset}") + self._b2_args_configured.add("toolset") + if self.pic: + warnings.warn("--pic is deprecated; use --b2-args=libtorrent-python-pic=on") + self._maybe_add_arg("libtorrent-python-pic=on") + self._b2_args_configured.add("libtorrent-python-pic") + if self.optimization: + warnings.warn( + "--optimization is deprecated; use --b2-args=optimization=..." + ) + self._maybe_add_arg(f"optimization={self.optimization}") + self._b2_args_configured.add("optimization") + if self.hash: + warnings.warn("--hash is deprecated; use --b2-args=--hash") + self._maybe_add_arg("--hash") + if self.cxxstd: + warnings.warn("--cxxstd is deprecated; use --b2-args=cxxstd=...") + self._maybe_add_arg(f"cxxstd={self.cxxstd}") + self._b2_args_configured.add("cxxstd") + + def _should_add_arg(self, arg): + m = re.match(r"(-\w).*", arg) + if m: + name = m.group(1) + else: + name = arg.split("=", 1)[0] + return name not in self._b2_args_configured + + def _maybe_add_arg(self, arg): + if self._should_add_arg(arg): + self._b2_args_split.append(arg) + return True + return False + + def run(self): + # The current jamfile layout just supports one extension + self._build_extension_with_b2() + return super().run() + + def _build_extension_with_b2(self): + python_binding_dir = pathlib.Path(__file__).parent.absolute() + with self._configure_b2(): + if self.linkflags: + for lf in self.linkflags: + # since b2 may be running with a different directory as cwd, + # relative + # paths need to be converted to absolute + if lf[2] != '/': + lf = '-L' + str(pathlib.Path(lf[2:]).absolute()) + self._b2_args_split.append("linkflags=" + lf) + if self.cxxflags: + for f in self.cxxflags: + self._b2_args_split.append("cxxflags=" + f) + command = ["b2"] + self._b2_args_split + log.info(" ".join(command)) + subprocess.run(command, cwd=python_binding_dir, check=True) + + @contextlib.contextmanager + def _configure_b2(self): + if self.config_mode == self.CONFIG_MODE_DISTUTILS: + # If we're using distutils mode, we'll auto-configure a lot of args + # and write temporary config. + yield from self._configure_b2_with_distutils() + else: + # If we're using b2 mode, no configuration needed + yield + + def _configure_b2_with_distutils(self): + if os.name == "nt": + self._maybe_add_arg("--abbreviate-paths") + + self._maybe_add_arg("boost-link=static") + self._maybe_add_arg("libtorrent-link=static") + + self._maybe_add_arg("crypto=openssl") + + if distutils.debug.DEBUG: + self._maybe_add_arg("--debug-configuration") + self._maybe_add_arg("--debug-building") + self._maybe_add_arg("--debug-generators") + + # Default feature configuration + self._maybe_add_arg("deprecated-functions=on") + + variant = "debug" if self.debug else "release" + self._maybe_add_arg(f"variant={variant}") + bits = 64 if sys.maxsize > 2 ** 32 else 32 + self._maybe_add_arg(f"address-model={bits}") + + # Cross-compiling logic: tricky, because autodetection is usually + # better than our matching + if sys.platform == "darwin": + # macOS uses multi-arch binaries. Attempt to match the + # configuration of the running python by translating distutils + # platform modes to b2 architecture modes + machine = distutils.util.get_platform().split("-")[-1] + if machine == "arm64": + self._maybe_add_arg("architecture=arm") + elif machine in ("ppc", "ppc64"): + self._maybe_add_arg("architecture=power") + elif machine in ("i386", "x86_64", "intel"): + self._maybe_add_arg("architecture=x86") + elif machine in ("universal", "fat", "fat3", "fat64"): + self._maybe_add_arg("architecture=combined") + # NB: as of boost 1.75.0, b2 doesn't have a straightforward way to + # build a "universal2" (arm64 + x86_64) binary + + if self.parallel: + self._maybe_add_arg(f"-j{self.parallel}") + + # We use a "project-config.jam" to instantiate a python environment + # to exactly match the running one. + override_project_config = False + if self._should_add_arg("--project-config"): + if self._maybe_add_arg(f"python={sysconfig.get_python_version()}"): + + override_project_config = True + + # Jamfile hacks to ensure we select the python environment defined in + # our project-config.jam + self._maybe_add_arg("libtorrent-python=on") + + # Our goal is to produce an artifact at this path. If we do this, the + # distutils build system will skip trying to build it. + target = pathlib.Path(self.get_ext_fullpath("libtorrent")).absolute() + self.announce(f"target: {target}") + + # b2 doesn't provide a way to signal the name or paths of its outputs. + # We try to convince python.jam to name its output file like our target + # and copy it to our target directory. See comments in + # write_b2_python_config for limitations on controlling the filename. + + # Jamfile hack to copy the module to our target directory + self._maybe_add_arg(f"python-install-path={target.parent}") + self._maybe_add_arg("install_module") + + # We use a "project-config.jam" to instantiate a python environment + # to exactly match the running one. + if override_project_config: + config = tempfile.NamedTemporaryFile(mode="w+", delete=False) + try: + write_b2_python_config(config) + config.seek(0) + log.info("project-config.jam contents:") + log.info(config.read()) + config.close() + self._b2_args_split.append(f"--project-config={config.name}") + yield + finally: + # If we errored while writing config, windows may complain about + # unlinking a file "in use" + config.close() + os.unlink(config.name) + else: + yield + + +setuptools.setup( + name="libtorrent", + version="2.0.6", + 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", + license="BSD", + ext_modules=[StubExtension("libtorrent")], + cmdclass={ + "build_ext": LibtorrentBuildExt, + }, + distclass=B2Distribution, +) 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..141ef87 --- /dev/null +++ b/bindings/python/src/alert.cpp @@ -0,0 +1,1139 @@ +// 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 "gil.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(); +} + +#if TORRENT_ABI_VERSION <= 2 +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; +} +#endif + +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.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.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) +{ + python_deprecated("resume_data is deprecated"); + 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) +#if TORRENT_ABI_VERSION <= 2 + POLY(stats_alert) +#endif + 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) + POLY(file_prio_alert) + +#if TORRENT_ABI_VERSION == 1 + POLY(anonymous_mode_alert) + POLY(torrent_added_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) + .def("__str__", &alert::message) + ; + + 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; +#if TORRENT_ABI_VERSION <= 2 + s.attr("stats_notification") = alert::stats_notification; +#endif + 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) +#if TORRENT_ABI_VERSION < 3 + .def_readonly("info_hash", &torrent_removed_alert::info_hash) +#endif + .def_readonly("info_hashes", &torrent_removed_alert::info_hashes) + ; + + 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("failure_reason", &tracker_error_alert::failure_reason) + .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) + .def("old_path", &storage_moved_alert::old_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) +#if TORRENT_ABI_VERSION < 3 + .def_readonly("info_hash", &torrent_deleted_alert::info_hash) +#endif + .def_readonly("info_hashes", &torrent_deleted_alert::info_hashes) + ; + + 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("socks5", socket_type_t::socks5) + .value("http", socket_type_t::http) + .value("utp", socket_type_t::utp) +#if TORRENT_ABI_VERSION <= 2 + .value("udp", socket_type_t::udp) +#endif + .value("i2p", socket_type_t::i2p) + .value("tcp_ssl", socket_type_t::tcp_ssl) + .value("socks5_ssl", socket_type_t::socks5_ssl) + .value("http_ssl", socket_type_t::http_ssl) + .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) + .def("old_name", &file_renamed_alert::old_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) +#if TORRENT_ABI_VERSION < 3 + .def_readonly("info_hash", &torrent_delete_failed_alert::info_hash) +#endif + .def_readonly("info_hashes", &torrent_delete_failed_alert::info_hashes) + ; + + 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) + ; + +#if TORRENT_ABI_VERSION <= 2 + 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 + ; +#endif // TORRENT_ABI_VERSION + + 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) + ; + + 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", depr(&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", depr(&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", depr(&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) + .add_property("module", make_getter(&dht_log_alert::module, by_value())) + .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) + .add_property("target", make_getter(&dht_immutable_item_alert::target, by_value())) + .add_property("item", &dht_immutable_item) + ; + + class_, noncopyable>( + "dht_mutable_item_alert", no_init) + .add_property("key", make_getter(&dht_mutable_item_alert::key, by_value())) + .add_property("signature", make_getter(&dht_mutable_item_alert::signature, by_value())) + .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) + .add_property("target", make_getter(&dht_put_alert::target, by_value())) + .add_property("public_key", make_getter(&dht_put_alert::public_key, by_value())) + .add_property("signature", make_getter(&dht_put_alert::signature, by_value())) + .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>( + "file_prio_alert", no_init) + ; + + 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..ed636e4 --- /dev/null +++ b/bindings/python/src/boost_python.hpp @@ -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) + +#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 + +inline void python_deprecated(char const* msg) +{ + if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1) == -1) + boost::python::throw_error_already_set(); +} + +#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..4d59128 --- /dev/null +++ b/bindings/python/src/converters.cpp @@ -0,0 +1,553 @@ +// 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 "libtorrent/storage_defs.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::make_address(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::make_address( + static_cast(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) + { + return incref(bp::object(addr.to_string()).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) + { + return +#if PY_VERSION_HEX < 0x03020000 + PyString_Check(x) ? x : +#endif + 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 PY_VERSION_HEX < 0x03020000 + if (PyString_Check(x)) + { + data->convertible = new (storage) lt::string_view( + PyString_AsString(x), PyString_Size(x)); + } + else +#endif + { + Py_ssize_t size = 0; + char const* unicode = PyUnicode_AsUTF8AndSize(x, &size); + data->convertible = new (storage) lt::string_view(unicode, size); + } + } +}; + +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*)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) 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 to_enum_class +{ + using underlying_type = typename std::underlying_type::type; + + to_enum_class() + { + 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(static_cast(static_cast(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, 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>, 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>(); + 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>>(); + to_python_converter>(); + +#if TORRENT_ABI_VERSION == 1 + to_python_converter>, vector_to_list>>>(); + list_to_vector>>(); + +#ifndef TORRENT_DISABLE_DHT + to_python_converter, vector_to_list>>(); +#endif +#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>>(); + list_to_vector>>(); + + // work-around types + list_to_vector>>(); + 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_enum_class(); +#if TORRENT_ABI_VERSION <= 2 + to_enum_class(); +#endif + 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(); + to_bitfield_flag(); + to_bitfield_flag(); +} diff --git a/bindings/python/src/create_torrent.cpp b/bindings/python/src/create_torrent.cpp new file mode 100644 index 0000000..65d82be --- /dev/null +++ b/bindings/python/src/create_torrent.cpp @@ -0,0 +1,277 @@ +// 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" +#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 +{ + void set_hash(create_torrent& c, piece_index_t p, bytes const& b) + { + c.set_hash(p, sha1_hash(b.arr)); + } + +#if TORRENT_ABI_VERSION < 3 + void set_file_hash(create_torrent& c, file_index_t f, bytes const& b) + { + c.set_file_hash(f, sha1_hash(b.arr)); + } +#endif + +#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) + { + python_deprecated("this overload of add_file() is deprecated"); + 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) + { + python_deprecated("__iter__ is deprecated"); + return FileIter(self, file_index_t(0)); + } + + FileIter end_files(file_storage const& self) + { return FileIter(self, self.end_file()); } +#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; + +#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 (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", depr(at)) + .def("add_file", add_file_deprecated, arg("entry")) + .def("__iter__", boost::python::range(&begin_files, &end_files)) + .def("__len__", depr(&file_storage::num_files)) +#endif // TORRENT_ABI_VERSION + .def("hash", file_storage_hash) + .def("symlink", file_storage_symlink) + .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("root", &file_storage::root) + .def("file_offset", file_storage_file_offset) + .def("file_flags", file_storage_file_flags) + + .def("file_index_for_root", &file_storage::file_index_for_root) + .def("piece_index_at_file", &file_storage::piece_index_at_file) + .def("file_index_at_piece", &file_storage::file_index_at_piece) + .def("file_index_at_offset", &file_storage::file_index_at_offset) + .def("file_absolute_path", &file_storage::file_absolute_path) + + .def("v2", &file_storage::v2) + + .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) + .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("flags") = create_flags_t{}))) + + .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) +#if TORRENT_ABI_VERSION < 3 + .def("set_file_hash", &set_file_hash) +#endif + .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) + + ; + +#if TORRENT_ABI_VERSION <= 2 + s.attr("optimize_alignment") = create_torrent::optimize_alignment; + s.attr("merkle") = create_torrent::merkle; +#endif + s.attr("v2_only") = create_torrent::v2_only; + s.attr("v1_only") = create_torrent::v1_only; + s.attr("canonical_files") = create_torrent::canonical_files; + s.attr("modification_time") = create_torrent::modification_time; + s.attr("symlinks") = create_torrent::symlinks; + s.attr("no_attributes") = create_torrent::no_attributes; + } + + { + scope s = class_("create_torrent_flags_t"); +#if TORRENT_ABI_VERSION == 1 + s.attr("optimize") = create_torrent::optimize; +#endif +#if TORRENT_ABI_VERSION <= 2 + s.attr("optimize_alignment") = create_torrent::optimize_alignment; + s.attr("merkle") = create_torrent::merkle; +#endif + s.attr("v2_only") = create_torrent::v2_only; + 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..454b22b --- /dev/null +++ b/bindings/python/src/datetime.cpp @@ -0,0 +1,144 @@ +// 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()))); + +#ifdef TORRENT_WINDOWS + std::tm const* date = localtime(&tm); +#else + std::tm buf; + std::tm const* date = localtime_r(&tm, &buf); +#endif + + 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..6e0ee74 --- /dev/null +++ b/bindings/python/src/entry.cpp @@ -0,0 +1,185 @@ +// 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); + } + else + { + // TODO: Throw a TypeError here in the future + python_deprecated("constructing a bencode entry from anything but " + "int, dict, list, string, bytes and int-tuple is deprecated"); + } + + 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..b947119 --- /dev/null +++ b/bindings/python/src/error_code.cpp @@ -0,0 +1,240 @@ +/* + +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 TORRENT_USE_SSL +#include +#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()); +#if TORRENT_USE_SSL + 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 + +#if TORRENT_ABI_VERSION == 1 + +#define WRAP_DEPR_CAT(name) \ + category_holder wrap_ ##name## _category_deprecated() { \ + python_deprecated(#name " is deprecated"); \ + return category_holder(name## _category()); \ + } + +WRAP_DEPR_CAT(libtorrent) +WRAP_DEPR_CAT(upnp) +WRAP_DEPR_CAT(http) +WRAP_DEPR_CAT(socks) +WRAP_DEPR_CAT(bdecode) +#if TORRENT_USE_I2P +WRAP_DEPR_CAT(i2p) +#endif +WRAP_DEPR_CAT(generic) +WRAP_DEPR_CAT(system) + +#undef WRAP_DEPR_CAT +#endif + +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_deprecated); + def("get_upnp_category", &wrap_upnp_category_deprecated); + def("get_http_category", &wrap_http_category_deprecated); + def("get_socks_category", &wrap_socks_category_deprecated); + def("get_bdecode_category", &wrap_bdecode_category_deprecated); +#if TORRENT_USE_I2P + def("get_i2p_category", &wrap_i2p_category_deprecated); +#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..dfe0707 --- /dev/null +++ b/bindings/python/src/fingerprint.cpp @@ -0,0 +1,34 @@ +// 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 "gil.hpp" +#include + +void bind_fingerprint() +{ + using namespace boost::python; + using namespace lt; + + def("generate_fingerprint", &generate_fingerprint); + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + class_("fingerprint", no_init) + .def( + init( + (arg("id"), "major", "minor", "revision", "tag") + ) + ) + .def("__str__", depr(&fingerprint::to_string)) + .def_readonly("major_version", depr(&fingerprint::major_version)) + .def_readonly("minor_version", depr(&fingerprint::minor_version)) + .def_readonly("revision_version", depr(&fingerprint::revision_version)) + .def_readonly("tag_version", depr(&fingerprint::tag_version)) + ; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // TORRENT_ABI_VERSION +} diff --git a/bindings/python/src/gil.hpp b/bindings/python/src/gil.hpp new file mode 100644 index 0000000..59f2f74 --- /dev/null +++ b/bindings/python/src/gil.hpp @@ -0,0 +1,186 @@ +// 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 + +#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(std::move(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); +} + +template::type>::value, int>::type = 0> +auto invoke(Fn&& fn, Self&& s, Args&&... args) -> +#if TORRENT_AUTO_RETURN_TYPES + decltype(auto) +#else + decltype((std::forward(s).*std::forward(fn))(std::forward(args)...)) +#endif +{ + return (std::forward(s).*std::forward(fn))(std::forward(args)...); +} + +template::type>::value, int>::type = 0> +auto invoke(Fn&& fn, Self&& s) -> +#if TORRENT_AUTO_RETURN_TYPES + decltype(auto) +#else + decltype((std::forward(s).*std::forward)(fn)) +#endif +{ + return (std::forward(s).*std::forward)(fn); +} + +template::type>::value, int>::type = 0> +auto invoke(Fn&& fn, Args&&... args) -> +#if TORRENT_AUTO_RETURN_TYPES + decltype(auto) +#else + decltype(std::forward(fn)(std::forward(args)...)) +#endif +{ + return std::forward(fn)(std::forward(args)...); +} + +template +struct deprecated_fun +{ + deprecated_fun(F fn, char const* name) : fn(fn), fn_name(name) {} + template + R operator()(Args&&... args) + { + std::string const msg = std::string(fn_name) + "() is deprecated"; + python_deprecated(msg.c_str()); + // TODO: in C++17 use std::invoke + return ::invoke(fn, std::forward(args)...); + } + F fn; + char const* fn_name; +}; + +template +struct deprecate_visitor : boost::python::def_visitor> +{ + deprecate_visitor(F fn) : fn(std::move(fn)) {} + + template + void visit_aux( + Class& cl, char const* name + , Options const& options, Signature const& signature) const + { + using return_type = typename boost::mpl::at_c::type; + + cl.def( + name + , boost::python::make_function( + deprecated_fun(fn, name) + , 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; +}; + +template +deprecate_visitor depr(F fn) +{ + return deprecate_visitor(std::move(fn)); +} + +//}} // namespace libtorrent::python + +#endif // GIL_070107_HPP diff --git a/bindings/python/src/info_hash.cpp b/bindings/python/src/info_hash.cpp new file mode 100644 index 0000000..6ea4772 --- /dev/null +++ b/bindings/python/src/info_hash.cpp @@ -0,0 +1,41 @@ +// Copyright Arvid Norberg 2020. 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 + +namespace { + +using namespace lt; + +long get_hash(info_hash_t const& ih) +{ + return std::hash{}(ih); +} + +} + +void bind_info_hash() +{ + using namespace boost::python; + using namespace lt; + + class_("info_hash_t") + .def(init(arg("sha1_hash"))) + .def(init(arg("sha256_hash"))) + .def(init((arg("sha1_hash"), arg("sha256_hash")))) + .def("__hash__", get_hash) + .def("has_v1", &info_hash_t::has_v1) + .def("has_v2", &info_hash_t::has_v2) + .def("has", &info_hash_t::has) + .def("get", &info_hash_t::get) + .def("get_best", &info_hash_t::get_best) + .add_property("v1", &info_hash_t::v1) + .add_property("v2", &info_hash_t::v2) + .def(self == self) + .def(self != self) + .def(self < self) + ; +} + diff --git a/bindings/python/src/ip_filter.cpp b/bindings/python/src/ip_filter.cpp new file mode 100644 index 0000000..766f844 --- /dev/null +++ b/bindings/python/src/ip_filter.cpp @@ -0,0 +1,49 @@ +// 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(make_address(start), make_address(end), flags); + } + + int access0(ip_filter& filter, std::string addr) + { + return filter.access(make_address(addr)); + } + + template + list convert_range_list(std::vector> const& l) + { + list ret; + for (auto const& r : l) + ret.append(boost::python::make_tuple(r.first.to_string(), r.last.to_string())); + return ret; + } + + tuple export_filter(ip_filter const& f) + { + auto ret = f.export_filter(); + list ipv4 = convert_range_list(std::get<0>(ret)); + list ipv6 = convert_range_list(std::get<1>(ret)); + return boost::python::make_tuple(ipv4, ipv6); + } +} + +void bind_ip_filter() +{ + class_("ip_filter") + .def("add_rule", &add_rule) + .def("access", &access0) + .def("export_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..7259331 --- /dev/null +++ b/bindings/python/src/magnet_uri.cpp @@ -0,0 +1,99 @@ +// 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) + { + python_deprecated("add_magnet_uri() is deprecated"); + 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; + if (p.info_hashes.has_v2()) + ret["info_hashes"] = bytes(p.info_hashes.v2.to_string()); + else + ret["info_hashes"] = bytes(p.info_hashes.v1.to_string()); +#if TORRENT_ABI_VERSION < 3 + ret["info_hash"] = bytes(p.info_hashes.get_best().to_string()); +#endif + 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; +#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..9546df7 --- /dev/null +++ b/bindings/python/src/module.cpp @@ -0,0 +1,60 @@ +// 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_sha256_hash(); +void bind_info_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_unicode_string_conversion(); + bind_error_code(); + bind_utility(); + bind_fingerprint(); + bind_sha1_hash(); + bind_sha256_hash(); + bind_info_hash(); + bind_entry(); + bind_torrent_handle(); + bind_session(); + bind_torrent_info(); + 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..0792395 --- /dev/null +++ b/bindings/python/src/peer_info.cpp @@ -0,0 +1,160 @@ +// 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) + .add_property("connection_type", make_getter(&peer_info::connection_type, by_value())) + .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("outgoing_connection") = peer_info::outgoing_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") = peer_info::standard_bittorrent; + pi.attr("web_seed") = peer_info::web_seed; + pi.attr("http_seed") = peer_info::http_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..ed1a51c --- /dev/null +++ b/bindings/python/src/session.cpp @@ -0,0 +1,1345 @@ +// 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 // 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(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; + +// defined in torrent_info.cpp +load_torrent_limits dict_to_limits(dict limits); + +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 + { + // if the dictionary doesn't contain "key", it will throw, hence + // the try-catch here + 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: + { + std::int64_t const val = extract(value); + // deliberately truncate and sign-convert here. If we + // extract an int directly, unsigned ints may throw + // an exception otherwise, if it doesn't fit. Notably for a + // flag-type with all bits set. + p.set_int(sett, static_cast(val)); + 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' && sett.has_val(i)) 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' && sett.has_val(i)) 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' && sett.has_val(i)) ret[name] = sett.get_bool(i); + } + return ret; + } + + std::shared_ptr make_session(boost::python::dict sett + , session_flags_t const flags) + { + settings_pack p; + make_settings_pack(p, sett); +#if TORRENT_ABI_VERSION <= 2 + if (flags & lt::session::add_default_plugins) + { + // TODO: this can't really be removed until there is a way to + // control plugins by exposing session_params. + // The simplest solution would probably be to make the default + // plugins not be plugins, but just bake in support for them +// python_deprecated("add_default_plugins flag is deprecated"); +#endif + session_params params(std::move(p)); + return std::make_shared(std::move(params), flags); +#if TORRENT_ABI_VERSION <= 2 + } + else + { + session_params params(std::move(p), {}); + return std::make_shared(std::move(params), flags); + } +#endif + } + + 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); + } +#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; + } +#if TORRENT_ABI_VERSION < 3 + else if (key == "info_hash") + { + if (boost::python::len(value) == sha1_hash::size()) + { + p.info_hash = sha1_hash(bytes(extract(value)).arr.data()); + } + } +#endif + else if (key == "info_hashes") + { + if (boost::python::len(value) == sha1_hash::size()) + { + p.info_hashes = info_hash_t(sha1_hash( + bytes(extract(value)).arr.data())); + } + else if (boost::python::len(value) == sha256_hash::size()) + { + p.info_hashes = info_hash_t(sha256_hash( + bytes(extract(value)).arr.data())); + } + else + { + p.info_hashes = boost::python::extract(value); + } + 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") + { + python_deprecated("the resume_data member is deprecated"); + std::string resume = extract(value); + p.resume_data.assign(resume.begin(), resume.end()); + 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") + { + python_deprecated("the url member is deprecated"); + 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(std::move(p)); +#else + error_code ec; + return s.add_torrent(std::move(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(std::move(p)); + } + + torrent_handle wrap_add_torrent(lt::session& s, lt::add_torrent_params const& p) + { + add_torrent_params atp = p; + if (p.ti) + atp.ti = std::make_shared(*p.ti); + + allow_threading_guard guard; + +#ifndef BOOST_NO_EXCEPTIONS + return s.add_torrent(std::move(p)); +#else + error_code ec; + return s.add_torrent(std::move(p), ec); +#endif + } + + void wrap_async_add_torrent(lt::session& s, lt::add_torrent_params const& p) + { + add_torrent_params atp = p; + if (p.ti) + atp.ti = std::make_shared(*p.ti); + + allow_threading_guard guard; + + s.async_add_torrent(std::move(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) + { + // keep a reference to the predicate here, in the python thread, to + // ensure it's freed in this thread at the end. If we move it into the + // libtorrent thread the python predicate will be freed from that + // thread, which won't work + auto wrapped_pred = std::bind(&wrap_pred, pred, std::placeholders::_1); + std::vector torrents + = s.get_torrent_status(std::ref(wrapped_pred), 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; + } + +#if TORRENT_ABI_VERSION == 1 + dict get_utp_stats(session_status const& st) + { + python_deprecated("session_status is deprecated"); + 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; + } +#endif + + entry save_state(lt::session const& s, std::uint32_t const flags) + { + entry e; +#if TORRENT_ABI_VERSION <= 2 + allow_threading_guard guard; + s.save_state(e, save_state_flags_t(flags)); +#endif + 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) + { +#if TORRENT_ABI_VERSION <= 2 + 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)); +#endif + } + + 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 + , [pk=std::move(public_key), sk=std::move(private_key), d=std::move(data)] + (entry& e, std::array& sig, std::int64_t& seq, std::string const& salt) + { + put_string(e, sig, seq, salt, pk, sk, d); + } + , salt); + } +#endif + + add_torrent_params read_resume_data_wrapper0(bytes const& b) + { + return read_resume_data(b.arr); + } + + add_torrent_params read_resume_data_wrapper1(bytes const& b, dict cfg) + { + return read_resume_data(b.arr, dict_to_limits(cfg)); + } + + 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; + } + + session_params read_session_params_entry(dict e + , save_state_flags_t flags) + { + entry ent = extract(e); + std::vector buf; + bencode(std::back_inserter(buf), ent); + return lt::read_session_params(buf, flags); + } + + session_params read_session_params_buffer(bytes const& bytes + , save_state_flags_t flags) + { + return lt::read_session_params(bytes.arr, flags); + } + + bytes write_session_params_bytes(session_params const& sp + , save_state_flags_t flags) + { + auto buf = write_session_params_buf(sp, flags); + return bytes(buf.data(), buf.size()); + } + + struct dict_to_settings + { + dict_to_settings() + { + 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*)data)->storage.bytes; + + dict o(borrowed(x)); + auto p = new (storage) lt::settings_pack; + data->convertible = p; + make_settings_pack(*p, o); + } + }; + + struct settings_to_dict + { + static PyObject* convert(lt::settings_pack const& p) + { + dict ret = make_dict(p); + return incref(ret.ptr()); + } + }; + +} // anonymous namespace + +struct dummy1 {}; +#if TORRENT_ABI_VERSION == 1 +struct dummy2 {}; +#endif +struct dummy9 {}; +struct dummy10 {}; +struct dummy11 {}; + +void bind_session() +{ + dict_to_settings(); + to_python_converter(); + +#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) + .add_property("active_requests", make_getter(&session_status::active_requests, return_value_policy())) + .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("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) +#if TORRENT_ABI_VERSION < 3 + .def_readwrite("info_hash", &add_torrent_params::info_hash) +#endif + .def_readwrite("info_hashes", &add_torrent_params::info_hashes) + .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)) +#if TORRENT_ABI_VERSION <= 2 + .add_property("merkle_tree", PROP(&add_torrent_params::merkle_tree)) +#endif + .add_property("renamed_files", PROP(&add_torrent_params::renamed_files)) + +#if TORRENT_ABI_VERSION == 1 + .def_readwrite("url", &add_torrent_params::url) + .add_property("resume_data", PROP(&add_torrent_params::resume_data)) +#endif + ; + +#ifndef TORRENT_DISABLE_DHT + class_("dht_state") + .add_property("nids", <::dht::dht_state::nids) + .add_property("nodes", <::dht::dht_state::nodes) + .add_property("nodes6", <::dht::dht_state::nodes6) + ; +#endif + + class_("session_params") + .def(init()) + .def(init<>()) + // TODO: since there's not binding for settings_pack, but they are just + // represented as dicts, this won't return a reference, but a copy of + // the settings + .add_property("settings", PROP(&session_params::settings)) +#ifndef TORRENT_DISABLE_DHT + .def_readwrite("dht_state", &session_params::dht_state) +#endif + .add_property("ext_state", PROP(&session_params::ext_state)) + .def_readwrite("ip_filter", &session_params::ip_filter) + ; + + def("read_session_params", &read_session_params_entry, (arg("dict"), arg("flags")=save_state_flags_t::all())); + def("read_session_params", &read_session_params_buffer, (arg("buffer"), arg("flags")=save_state_flags_t::all())); + def("write_session_params", <::write_session_params, (arg("entry"), arg("flags")=save_state_flags_t::all())); + def("write_session_params_buf", &write_session_params_bytes, (arg("buffer"), arg("flags")=save_state_flags_t::all())); + + 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("paused") = lt::session::paused; +#if TORRENT_ABI_VERSION <= 2 + s.attr("add_default_plugins") = lt::session::add_default_plugins; +#endif +#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("no_verify_files") = torrent_flags::no_verify_files; + 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 + ; + + 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()) + .def(init<>()) + .def("__init__", boost::python::make_constructor(&make_session + , default_call_policies() + , (arg("settings"), arg("flags")= +#if TORRENT_ABI_VERSION <= 2 + lt::session::add_default_plugins +#else + lt::session_flags_t{} +#endif + )) + ) +#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", depr(&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", depr(&add_dht_router) + , (arg("router"), "port") + ) +#endif // TORRENT_ABI_VERSION + .def("is_dht_running", allow_threads(<::session::is_dht_running)) +#if TORRENT_ABI_VERSION <= 2 + .def("set_dht_settings", allow_threads(<::session::set_dht_settings)) + .def("get_dht_settings", allow_threads(<::session::get_dht_settings)) +#endif + .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", &wrap_async_add_torrent) + .def("add_torrent", &wrap_add_torrent) +#ifndef BOOST_NO_EXCEPTIONS +#if TORRENT_ABI_VERSION == 1 + .def( + "add_torrent", depr(&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", depr(<::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", depr(<::session::set_pe_settings)) + .def("get_pe_settings", depr(<::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", depr(<::session::set_i2p_proxy)) + .def("i2p_proxy", depr(<::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("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", depr(<::session::id)) + .def( + "listen_on", depr(&listen_on) + , (arg("min"), "max", arg("interface") = (char const*)nullptr, arg("flags") = 0) + ) +#ifndef TORRENT_DISABLE_DHT + .def("start_dht", depr(start_dht0)) + .def("stop_dht", depr(<::session::stop_dht)) + .def("start_dht", depr(start_dht1)) + .def("dht_state", depr(<::session::dht_state)) + .def("set_dht_proxy", depr(<::session::set_dht_proxy)) + .def("dht_proxy", depr(<::session::dht_proxy)) +#endif + .def("set_local_download_rate_limit", depr(<::session::set_local_download_rate_limit)) + .def("local_download_rate_limit", depr(<::session::local_download_rate_limit)) + .def("set_local_upload_rate_limit", depr(<::session::set_local_upload_rate_limit)) + .def("local_upload_rate_limit", depr(<::session::local_upload_rate_limit)) + .def("set_download_rate_limit", depr(<::session::set_download_rate_limit)) + .def("download_rate_limit", depr(<::session::download_rate_limit)) + .def("set_upload_rate_limit", depr(<::session::set_upload_rate_limit)) + .def("upload_rate_limit", depr(<::session::upload_rate_limit)) + .def("set_max_uploads", depr(<::session::set_max_uploads)) + .def("set_max_connections", depr(<::session::set_max_connections)) + .def("max_connections", depr(<::session::max_connections)) + .def("num_connections", depr(<::session::num_connections)) + .def("set_max_half_open_connections", depr(<::session::set_max_half_open_connections)) + .def("set_alert_queue_size_limit", depr(<::session::set_alert_queue_size_limit)) + .def("set_alert_mask", depr(<::session::set_alert_mask)) + .def("set_peer_proxy", depr(<::session::set_peer_proxy)) + .def("set_tracker_proxy", depr(<::session::set_tracker_proxy)) + .def("set_web_seed_proxy", depr(<::session::set_web_seed_proxy)) + .def("peer_proxy", depr(<::session::peer_proxy)) + .def("tracker_proxy", depr(<::session::tracker_proxy)) + .def("web_seed_proxy", depr(<::session::web_seed_proxy)) + .def("set_proxy", depr(<::session::set_proxy)) + .def("proxy", depr(<::session::proxy)) + .def("start_upnp", depr(&start_upnp)) + .def("stop_upnp", depr(<::session::stop_upnp)) + .def("start_lsd", depr(<::session::start_lsd)) + .def("stop_lsd", depr(<::session::stop_lsd)) + .def("start_natpmp", depr(&start_natpmp)) + .def("stop_natpmp", depr(<::session::stop_natpmp)) + .def("set_peer_id", depr(<::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; +#if TORRENT_ABI_VERSION <= 2 + s.attr("save_dht_settings") = lt::session::save_dht_settings; +#endif + 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_wrapper0); + def("read_resume_data", read_resume_data_wrapper1); + 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..46be488 --- /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) + .value("write_through", settings_pack::write_through) + ; + + 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 +#if TORRENT_ABI_VERSION <= 2 + class_("dht_settings") + .def_readwrite("max_peers_reply", &dht::dht_settings::max_peers_reply) + .def_readwrite("search_branching", &dht::dht_settings::search_branching) + .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 +#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..9fa4d0c --- /dev/null +++ b/bindings/python/src/sha1_hash.cpp @@ -0,0 +1,46 @@ +// 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" + +namespace { + +using namespace lt; + +long get_hash(sha1_hash const& s) +{ + return std::hash{}(s); +} + +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_bytes) + .def("__hash__", get_hash) + .def("to_bytes", sha1_hash_bytes) + ; + + scope().attr("peer_id") = scope().attr("sha1_hash"); +} + diff --git a/bindings/python/src/sha256_hash.cpp b/bindings/python/src/sha256_hash.cpp new file mode 100644 index 0000000..5da2ad7 --- /dev/null +++ b/bindings/python/src/sha256_hash.cpp @@ -0,0 +1,44 @@ +// 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" + +namespace { + +using namespace lt; + +long get_hash(sha256_hash const& s) +{ + return std::hash{}(s); +} + +bytes sha256_hash_bytes(const sha256_hash& bn) { + return bytes(bn.to_string()); +} + +} + +void bind_sha256_hash() +{ + using namespace boost::python; + using namespace lt; + + class_("sha256_hash") + .def(self == self) + .def(self != self) + .def(self < self) + .def(self_ns::str(self)) + .def(init()) + .def("clear", &sha256_hash::clear) + .def("is_all_zeros", &sha256_hash::is_all_zeros) + .def("to_string", sha256_hash_bytes) + .def("__hash__", get_hash) + .def("to_bytes", sha256_hash_bytes) + ; +} + diff --git a/bindings/python/src/string.cpp b/bindings/python/src/string.cpp new file mode 100644 index 0000000..408b014 --- /dev/null +++ b/bindings/python/src/string.cpp @@ -0,0 +1,53 @@ +// 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 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 PY_VERSION_HEX < 0x03000000 + if (PyString_Check(x)) + { + data->convertible = new (storage) std::string(PyString_AsString(x) + , PyString_Size(x)); + } + else +#endif + { + Py_ssize_t size = 0; + char const* unicode = PyUnicode_AsUTF8AndSize(x, &size); + data->convertible = new (storage) std::string(unicode, size); + } + } +}; + +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..4ae9088 --- /dev/null +++ b/bindings/python/src/torrent_handle.cpp @@ -0,0 +1,661 @@ +// 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 "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, file_progress_flags_t const 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 +{ + using std::chrono::system_clock; + + object to_ptime(time_point tpt) + { + object ret; + if (tpt > min_time()) + { + ret = long_(system_clock::to_time_t(system_clock::now() + + duration_cast(tpt - clock_type::now()))); + } + return ret; + } +} + +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(); + announce_infohash const& aih = aep.info_hashes[protocol_version::V1]; + d["message"] = aih.message; + dict last_error; + last_error["value"] = aih.last_error.value(); + last_error["category"] = aih.last_error.category().name(); + d["last_error"] = last_error; + d["next_announce"] = to_ptime(aih.next_announce); + d["min_announce"] = to_ptime(aih.min_announce); + d["scrape_incomplete"] = aih.scrape_incomplete; + d["scrape_complete"] = aih.scrape_complete; + d["scrape_downloaded"] = aih.scrape_downloaded; + d["fails"] = aih.fails; + d["updating"] = aih.updating; + d["start_sent"] = aih.start_sent; + d["complete_sent"] = aih.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["local_address"] = boost::python::make_tuple(aep.local_endpoint.address().to_string(), aep.local_endpoint.port()); + + list aihs; + for (auto const& aih : aep.info_hashes) + { + dict i; + i["message"] = aih.message; + dict last_error; + last_error["value"] = aih.last_error.value(); + last_error["category"] = aih.last_error.category().name(); + i["last_error"] = last_error; + i["next_announce"] = to_ptime(aih.next_announce); + i["min_announce"] = to_ptime(aih.min_announce); + i["scrape_incomplete"] = aih.scrape_incomplete; + i["scrape_complete"] = aih.scrape_complete; + i["scrape_downloaded"] = aih.scrape_downloaded; + i["fails"] = aih.fails; + i["updating"] = aih.updating; + i["start_sent"] = aih.start_sent; + i["complete_sent"] = aih.complete_sent; + aihs.append(std::move(i)); + } + e["info_hashes"] = std::move(aihs); + +#if TORRENT_ABI_VERSION <= 2 + announce_infohash const& aih = aep.info_hashes[protocol_version::V1]; + e["message"] = aih.message; + dict last_error; + last_error["value"] = aih.last_error.value(); + last_error["category"] = aih.last_error.category().name(); + e["last_error"] = last_error; + e["next_announce"] = to_ptime(aih.next_announce); + e["min_announce"] = to_ptime(aih.min_announce); + e["scrape_incomplete"] = aih.scrape_incomplete; + e["scrape_complete"] = aih.scrape_complete; + e["scrape_downloaded"] = aih.scrape_downloaded; + e["fails"] = aih.fails; + e["updating"] = aih.updating; + e["start_sent"] = aih.start_sent; + e["complete_sent"] = aih.complete_sent; +#endif + aeps.append(std::move(e)); + } + d["endpoints"] = std::move(aeps); + +#if TORRENT_ABI_VERSION == 1 + d["send_stats"] = i->send_stats; +#endif + ret.append(std::move(d)); + } + return ret; +} + +list get_download_queue(torrent_handle& handle) +{ + list ret; + + std::vector downloading; + + { + allow_threading_guard guard; + downloading = handle.get_download_queue(); + } + + 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 {}; +class dummy16 {}; + +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; + + 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") = file_progress_flags_t{}) + .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("info_hashes", _(&torrent_handle::info_hashes)) + .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", depr(&piece_priorities)) + .def("file_priorities", depr(&file_priorities)) + .def("stop_when_ready", depr(&torrent_handle::stop_when_ready)) + .def("super_seeding", depr(super_seeding1)) + .def("auto_managed", depr(&torrent_handle::auto_managed)) + .def("set_priority", depr(&torrent_handle::set_priority)) + .def("get_torrent_info", depr(&get_torrent_info)) + .def("super_seeding", depr(super_seeding0)) + .def("write_resume_data", depr(&torrent_handle::write_resume_data)) + .def("is_seed", depr(&torrent_handle::is_seed)) + .def("is_finished", depr(&torrent_handle::is_finished)) + .def("has_metadata", depr(&torrent_handle::has_metadata)) + .def("use_interface", depr(&torrent_handle::use_interface)) + .def("name", depr(&torrent_handle::name)) + .def("is_paused", depr(&torrent_handle::is_paused)) + .def("is_auto_managed", depr(&torrent_handle::is_auto_managed)) + .def("set_upload_mode", depr(&torrent_handle::set_upload_mode)) + .def("set_share_mode", depr(&torrent_handle::set_share_mode)) + .def("apply_ip_filter", depr(&torrent_handle::apply_ip_filter)) + .def("set_sequential_download", depr(&torrent_handle::set_sequential_download)) + .def("set_peer_upload_limit", depr(&torrent_handle::set_peer_upload_limit)) + .def("set_peer_download_limit", depr(&torrent_handle::set_peer_download_limit)) + .def("set_ratio", depr(&torrent_handle::set_ratio)) + .def("save_path", depr(&torrent_handle::save_path)) + .def("set_tracker_login", depr(&torrent_handle::set_tracker_login)) +#endif + ; + + s.attr("ignore_min_interval") = torrent_handle::ignore_min_interval; + s.attr("overwrite_existing") = torrent_handle::overwrite_existing; + s.attr("piece_granularity") = 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 + } + + { + scope s = class_("file_progress_flags_t"); + s.attr("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..6509e05 --- /dev/null +++ b/bindings/python/src/torrent_info.cpp @@ -0,0 +1,514 @@ +// 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 "libtorrent/info_hash.hpp" // for protocol_version +#include "libtorrent/tracker_manager.hpp" // for event_t +#include "bytes.hpp" +#include "gil.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); + } + +#if TORRENT_ABI_VERSION <= 2 + 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); + } +#endif + + bytes hash_for_piece(torrent_info const& ti, piece_index_t i) + { + return bytes(ti.hash_for_piece(i).to_string()); + } + +#if TORRENT_ABI_VERSION <= 2 + bytes metadata(torrent_info const& ti) + { + auto const s = ti.info_section(); + return bytes(s.data(), s.size()); + } +#endif + + bytes get_info_section(torrent_info const& ti) + { + auto const s = ti.info_section(); + return bytes(s.data(), s.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) + { + python_deprecated("next_announce is deprecated"); + return ae.endpoints.empty() ? lt::time_point() : lt::time_point(ae.endpoints.front().info_hashes[protocol_version::V1].next_announce); + } + lt::time_point get_min_announce(announce_entry const& ae) + { + python_deprecated("min_announce is deprecated"); + return ae.endpoints.empty() ? lt::time_point() : lt::time_point(ae.endpoints.front().info_hashes[protocol_version::V1].min_announce); + } + // announce_entry data member bit-fields. + int get_fails(announce_entry const& ae) + { + python_deprecated("fails is deprecated"); + return ae.endpoints.empty() ? 0 : ae.endpoints.front().info_hashes[protocol_version::V1].fails; + } + bool get_updating(announce_entry const& ae) + { + python_deprecated("updating is deprecated"); + return ae.endpoints.empty() ? false : ae.endpoints.front().info_hashes[protocol_version::V1].updating; + } + bool get_start_sent(announce_entry const& ae) + { + python_deprecated("start_sent is deprecated"); + return ae.endpoints.empty() ? false : ae.endpoints.front().info_hashes[protocol_version::V1].start_sent; + } + bool get_complete_sent(announce_entry const& ae) + { + python_deprecated("complete_sent is deprecated"); + return ae.endpoints.empty() ? false : ae.endpoints.front().info_hashes[protocol_version::V1].complete_sent; + } + // announce_entry method requires lt::time_point. + bool can_announce(announce_entry const& ae, bool is_seed) { + python_deprecated("can_announce() is deprecated"); + // 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) + { + python_deprecated("is_working is deprecated"); + 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) + { + python_deprecated("message is deprecated"); + return ae.endpoints.empty() ? "" : ae.endpoints.front().info_hashes[protocol_version::V1].message; + } + error_code get_last_error(announce_entry const& ae) + { + python_deprecated("last_error is deprecated"); + return ae.endpoints.empty() ? error_code() : ae.endpoints.front().info_hashes[protocol_version::V1].last_error; + } + int get_scrape_incomplete(announce_entry const& ae) + { + python_deprecated("scrape_incomplete is deprecated"); + return ae.endpoints.empty() ? 0 : ae.endpoints.front().info_hashes[protocol_version::V1].scrape_incomplete; + } + int get_scrape_complete(announce_entry const& ae) + { + python_deprecated("scrape_complete is deprecated"); + return ae.endpoints.empty() ? 0 : ae.endpoints.front().info_hashes[protocol_version::V1].scrape_complete; + } + int get_scrape_downloaded(announce_entry const& ae) + { + python_deprecated("scrape_downloaded is deprecated"); + return ae.endpoints.empty() ? 0 : ae.endpoints.front().info_hashes[protocol_version::V1].scrape_downloaded; + } + int next_announce_in(announce_entry const&) + { + python_deprecated("next_announce_in is deprecated"); + return 0; + } + int min_announce_in(announce_entry const&) + { + python_deprecated("min_announce_in is deprecated"); + return 0; + } + bool get_send_stats(announce_entry const& ae) + { + python_deprecated("send_stats is deprecated"); + return ae.send_stats; + } + std::int64_t get_size(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.size; + } + std::int64_t get_offset(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.offset; + } + bool get_pad_file(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.pad_file; + } + bool get_executable_attribute(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.executable_attribute; + } + bool get_hidden_attribute(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.hidden_attribute; + } + bool get_symlink_attribute(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.symlink_attribute; + } +#endif + +} // namespace unnamed + +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; +} + +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(lt::string_view filename) +{ + return std::make_shared(std::string(filename)); +} + +std::shared_ptr file_constructor1(lt::string_view filename, dict limits) +{ + return std::make_shared(std::string(filename), dict_to_limits(limits)); +} + +std::shared_ptr sha1_constructor0(sha1_hash const& ih) +{ + return std::make_shared(info_hash_t(ih)); +} + +std::shared_ptr sha256_constructor0(sha256_hash const& ih) +{ + return std::make_shared(info_hash_t(ih)); +} + +std::shared_ptr bencoded_constructor0(dict d) +{ + entry ent = extract(d); + std::vector buf; + bencode(std::back_inserter(buf), ent); + return std::make_shared(buf, lt::from_span); +} + +std::shared_ptr bencoded_constructor1(dict d, dict limits) +{ + entry ent = extract(d); + 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; + + 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_("protocol_version") + .value("V1", protocol_version::V1) + .value("V2", protocol_version::V2) + ; + + 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")))) + + .def("__init__", make_constructor(&sha1_constructor0)) + .def("__init__", make_constructor(&sha256_constructor0)) + + .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, (arg("url") + , arg("extern_auth") = std::string{} + , arg("extra_headers") = web_seed_entry::headers_t{})) + .def("add_http_seed", &torrent_info::add_http_seed, (arg("url") + , arg("extern_auth") = std::string{} + , arg("extra_headers") = web_seed_entry::headers_t{})) + .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) + .def("info_hashes", &torrent_info::info_hashes, copy) + .def("hash_for_piece", &hash_for_piece) +#if TORRENT_ABI_VERSION <= 2 + .def("merkle_tree", depr(&get_merkle_tree)) + .def("set_merkle_tree", depr(&set_merkle_tree)) +#endif + .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", depr(&torrent_info::file_at)) +#endif // TORRENT_ABI_VERSION + + .def("is_valid", &torrent_info::is_valid) + .def("priv", &torrent_info::priv) + .def("is_i2p", &torrent_info::is_i2p) +#if TORRENT_ABI_VERSION <= 2 + .def("is_merkle_torrent", depr(&torrent_info::is_merkle_torrent)) +#endif + .def("trackers", range(begin_trackers, end_trackers)) + + .def("creation_date", &torrent_info::creation_date) + + .def("add_node", &add_node) + .def("nodes", &nodes) +#if TORRENT_ABI_VERSION <= 2 + .def("metadata", depr(&metadata)) + .def("metadata_size", depr(&torrent_info::metadata_size)) +#endif + .def("info_section", &get_info_section) + .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", depr(&next_announce_in)) + .def("min_announce_in", depr(&min_announce_in)) + .def("can_announce", depr(&can_announce)) + .def("is_working", depr(&is_working)) +#endif +#if TORRENT_ABI_VERSION <= 2 + .def("reset", depr(&announce_entry::reset)) + .def("trim", depr(&announce_entry::trim)) +#endif + ; + + enum_("event_t") + .value("none", event_t::none) + .value("completed", event_t::completed) + .value("started", event_t::started) + .value("stopped", event_t::stopped) + .value("paused", event_t::paused) + ; + + 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..6e48275 --- /dev/null +++ b/bindings/python/src/torrent_status.cpp @@ -0,0 +1,143 @@ +// 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 "gil.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) + .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", &torrent_status::total) + .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) +#if TORRENT_ABI_VERSION < 3 + .def_readonly("info_hash", &torrent_status::info_hash) +#endif + .def_readonly("info_hashes", &torrent_status::info_hashes) + .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) +#if TORRENT_ABI_VERSION == 1 + .value("allocating", torrent_status::allocating) +#endif + .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..0e8f513 --- /dev/null +++ b/bindings/python/src/utility.cpp @@ -0,0 +1,130 @@ +// 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; + } +}; + +template +struct array_to_python +{ + static PyObject* convert(std::array const& p) + { +#if PY_MAJOR_VERSION >= 3 + PyObject *ret = PyBytes_FromStringAndSize(p.data(), p.size()); +#else + PyObject *ret = PyString_FromStringAndSize(p.data(), p.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) || PyByteArray_Check(x)) ? x : nullptr; +#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(); + if (PyByteArray_Check(x)) + { + ret->arr.resize(PyByteArray_Size(x)); + memcpy(&ret->arr[0], PyByteArray_AsString(x), ret->arr.size()); + } + else + { + 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) +{ + python_deprecated("client_fingerprint is deprecated"); + 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(); + to_python_converter, array_to_python<32>>(); + to_python_converter, array_to_python<64>>(); + 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..832a526 --- /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") = lt::version_str; + scope().attr("version_major") = LIBTORRENT_VERSION_MAJOR; + scope().attr("version_minor") = LIBTORRENT_VERSION_MINOR; +#endif +} + diff --git a/bindings/python/test.py b/bindings/python/test.py new file mode 100644 index 0000000..243f00a --- /dev/null +++ b/bindings/python/test.py @@ -0,0 +1,1331 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + + +import libtorrent as lt + +import unittest +import time +import datetime +import os +import shutil +import binascii +import subprocess as sub +import sys +import pickle +import threading +import tempfile +import socket +import select +import logging +import ssl +import http.server +import functools + +import dummy_data + +# include terminal interface for travis parallel executions of scripts which use +# terminal features: fix multiple stdin assignment at termios.tcgetattr +if os.name != 'nt': + import pty + +settings = { + 'alert_mask': lt.alert.category_t.all_categories, + 'enable_dht': False, 'enable_lsd': False, 'enable_natpmp': False, + 'enable_upnp': False, 'listen_interfaces': '0.0.0.0:0', 'file_pool_size': 1} + + +def has_deprecated(): + return hasattr(lt, 'version') + + +class test_create_torrent(unittest.TestCase): + + def test_from_torrent_info(self): + ti = lt.torrent_info('unordered.torrent') + print(ti.ssl_cert()) + ct = lt.create_torrent(ti) + entry = ct.generate() + content = lt.bencode(entry).strip() + with open('unordered.torrent', 'rb') as f: + file_content = bytearray(f.read().strip()) + print(content) + print(file_content) + print(entry) + self.assertEqual(content, file_content) + + def test_from_scratch(self): + fs = lt.file_storage() + fs.add_file('test/file1', 1000) + fs.add_file('test/file2', 2000) + self.assertEqual(fs.file_name(0), 'file1') + self.assertEqual(fs.file_name(1), 'file2') + ct = lt.create_torrent(fs) + ct.add_url_seed('foo') + ct.add_http_seed('bar') + ct.add_tracker('bar') + ct.set_root_cert('1234567890') + ct.add_collection('1337') + for i in range(ct.num_pieces()): + ct.set_hash(i, b'abababababababababab') + entry = ct.generate() + encoded = lt.bencode(entry) + print(encoded) + + # zero out the creation date: + encoded = encoded.split(b'13:creation datei', 1) + encoded[1] = b'0e' + encoded[1].split(b'e', 1)[1] + encoded = b'13:creation datei'.join(encoded) + + self.assertEqual(encoded, b'd8:announce3:bar13:creation datei0e9:httpseeds3:bar4:infod11:collectionsl4:1337e5:filesld6:lengthi1000e4:pathl5:file1eed4:attr1:p6:lengthi15384e4:pathl4:.pad5:15384eed6:lengthi2000e4:pathl5:file2eee4:name4:test12:piece lengthi16384e6:pieces40:abababababababababababababababababababab8:ssl-cert10:1234567890e8:url-list3:fooe') + + +class test_session_stats(unittest.TestCase): + + def test_add_torrent_params(self): + atp = lt.add_torrent_params() + + for field_name in dir(atp): + field = getattr(atp, field_name) + print(field_name, field) + + atp.renamed_files = {} + atp.merkle_tree = [] + atp.unfinished_pieces = {} + atp.have_pieces = [] + atp.banned_peers = [] + atp.verified_pieces = [] + atp.piece_priorities = [] + atp.url_seeds = [] + + def test_unique(self): + metrics = lt.session_stats_metrics() + self.assertTrue(len(metrics) > 40) + idx = set() + for m in metrics: + self.assertTrue(m.value_index not in idx) + idx.add(m.value_index) + + def test_find_idx(self): + self.assertEqual(lt.find_metric_idx("peer.error_peers"), 0) + + +class test_torrent_handle(unittest.TestCase): + + def setup(self): + self.ses = lt.session(settings) + self.ti = lt.torrent_info('url_seed_multi.torrent') + self.h = self.ses.add_torrent({ + 'ti': self.ti, 'save_path': os.getcwd(), + 'flags': lt.torrent_flags.default_flags}) + + def test_add_torrent_error(self): + self.ses = lt.session(settings) + self.ti = lt.torrent_info('url_seed_multi.torrent') + with self.assertRaises(RuntimeError): + self.ses.add_torrent({'ti': self.ti, 'save_path': os.getcwd(), 'info_hashes': b'abababababababababab'}) + + def test_move_storage(self): + self.setup() + self.h.move_storage(u'test-dir') + self.h.move_storage(b'test-dir2') + self.h.move_storage('test-dir3') + self.h.move_storage(u'test-dir', flags=lt.move_flags_t.dont_replace) + self.h.move_storage(u'test-dir', flags=2) + self.h.move_storage(b'test-dir2', flags=2) + self.h.move_storage('test-dir3', flags=2) + + def test_torrent_handle(self): + self.setup() + self.assertEqual(self.h.get_file_priorities(), [4, 4]) + self.assertEqual(self.h.get_piece_priorities(), [4]) + + self.h.prioritize_files([0, 1]) + # workaround for asynchronous priority update + time.sleep(1) + self.assertEqual(self.h.get_file_priorities(), [0, 1]) + + self.h.prioritize_pieces([0]) + self.assertEqual(self.h.get_piece_priorities(), [0]) + + # also test the overload that takes a list of piece->priority mappings + self.h.prioritize_pieces([(0, 1)]) + self.assertEqual(self.h.get_piece_priorities(), [1]) + self.h.connect_peer(('127.0.0.1', 6881)) + self.h.connect_peer(('127.0.0.2', 6881), source=4) + self.h.connect_peer(('127.0.0.3', 6881), flags=2) + self.h.connect_peer(('127.0.0.4', 6881), flags=2, source=4) + + torrent_files = self.h.torrent_file() + print(torrent_files.map_file(0, 0, 0).piece) + + print(self.h.queue_position()) + + def test_torrent_handle_in_set(self): + self.setup() + torrents = set() + torrents.add(self.h) + + # get another instance of a torrent_handle that represents the same + # torrent. Make sure that when we add it to a set, it just replaces the + # existing object + t = self.ses.get_torrents() + self.assertEqual(len(t), 1) + for h in t: + torrents.add(h) + + self.assertEqual(len(torrents), 1) + + def test_torrent_handle_in_dict(self): + self.setup() + torrents = {} + torrents[self.h] = 'foo' + + # get another instance of a torrent_handle that represents the same + # torrent. Make sure that when we add it to a dict, it just replaces the + # existing object + t = self.ses.get_torrents() + self.assertEqual(len(t), 1) + for h in t: + torrents[h] = 'bar' + + self.assertEqual(len(torrents), 1) + self.assertEqual(torrents[self.h], 'bar') + + def test_replace_trackers(self): + self.setup() + trackers = [] + for idx, tracker_url in enumerate(('udp://tracker1.com', 'udp://tracker2.com')): + tracker = lt.announce_entry(tracker_url) + tracker.tier = idx + tracker.fail_limit = 2 + trackers.append(tracker) + self.assertEqual(tracker.url, tracker_url) + self.h.replace_trackers(trackers) + new_trackers = self.h.trackers() + self.assertEqual(new_trackers[0]['url'], 'udp://tracker1.com') + self.assertEqual(new_trackers[1]['tier'], 1) + self.assertEqual(new_trackers[1]['fail_limit'], 2) + + def test_pickle_trackers(self): + """Test lt objects convertors are working and trackers can be pickled""" + self.setup() + tracker = lt.announce_entry('udp://tracker1.com') + tracker.tier = 0 + tracker.fail_limit = 1 + trackers = [tracker] + self.h.replace_trackers(trackers) + # wait a bit until the endpoints list gets populated + while len(self.h.trackers()[0]['endpoints']) == 0: + time.sleep(0.1) + + trackers = self.h.trackers() + self.assertEqual(trackers[0]['url'], 'udp://tracker1.com') + # this is not necessarily 0, it could also be (EHOSTUNREACH) if the + # local machine doesn't support the address family + expect_value = trackers[0]['endpoints'][0]['info_hashes'][0]['last_error']['value'] + pickled_trackers = pickle.dumps(trackers) + unpickled_trackers = pickle.loads(pickled_trackers) + self.assertEqual(unpickled_trackers[0]['url'], 'udp://tracker1.com') + self.assertEqual(unpickled_trackers[0]['endpoints'][0]['info_hashes'][0]['last_error']['value'], expect_value) + + def test_file_status(self): + self.setup() + status = self.h.file_status() + print(status) + + def test_piece_deadlines(self): + self.setup() + self.h.clear_piece_deadlines() + + def test_status_last_uploaded_dowloaded(self): + # we want to check at seconds precision but can't control session + # time, wait for next full second to prevent second increment + time.sleep(1 - datetime.datetime.now().microsecond / 1000000.0) + + self.setup() + st = self.h.status() + for attr in dir(st): + print('%s: %s' % (attr, getattr(st, attr))) + # last upload and download times are at session start time + self.assertEqual(st.last_upload, None) + self.assertEqual(st.last_download, None) + + def test_serialize_trackers(self): + """Test to ensure the dict contains only python built-in types""" + self.setup() + self.h.add_tracker({'url': 'udp://tracker1.com'}) + tr = self.h.trackers()[0] + # wait a bit until the endpoints list gets populated + while len(tr['endpoints']) == 0: + time.sleep(0.1) + tr = self.h.trackers()[0] + import json + print(json.dumps(self.h.trackers()[0])) + + def test_torrent_status(self): + self.setup() + st = self.h.status() + ti = st.handle + self.assertEqual(ti.info_hashes(), self.ti.info_hashes()) + # make sure we can compare torrent_status objects + st2 = self.h.status() + self.assertEqual(st2, st) + print(st2) + + def test_read_resume_data(self): + + resume_data = lt.bencode({ + 'file-format': 'libtorrent resume file', + 'info-hash': 'abababababababababab', + 'name': 'test', + 'save_path': '.', + 'peers': '\x01\x01\x01\x01\x00\x01\x02\x02\x02\x02\x00\x02', + 'file_priority': [0, 1, 1]}) + tp = lt.read_resume_data(resume_data) + + self.assertEqual(tp.name, 'test') + self.assertEqual(tp.info_hashes.v1, lt.sha1_hash('abababababababababab')) + self.assertEqual(tp.file_priorities, [0, 1, 1]) + self.assertEqual(tp.peers, [('1.1.1.1', 1), ('2.2.2.2', 2)]) + + ses = lt.session(settings) + h = ses.add_torrent(tp) + for attr in dir(tp): + print('%s: %s' % (attr, getattr(tp, attr))) + + h.connect_peer(('3.3.3.3', 3)) + + for i in range(0, 10): + alerts = ses.pop_alerts() + for a in alerts: + print(a.message()) + time.sleep(0.1) + + def test_scrape(self): + self.setup() + # this is just to make sure this function can be called like this + # from python + self.h.scrape_tracker() + + def test_unknown_torrent_parameter(self): + self.ses = lt.session(settings) + try: + self.h = self.ses.add_torrent({'unexpected-key-name': ''}) + self.assertFalse('should have thrown an exception') + except KeyError as e: + print(e) + + def test_torrent_parameter(self): + self.ses = lt.session(settings) + self.ti = lt.torrent_info('url_seed_multi.torrent') + self.h = self.ses.add_torrent({ + 'ti': self.ti, + 'save_path': os.getcwd(), + 'trackers': ['http://test.com/announce'], + 'dht_nodes': [('1.2.3.4', 6881), ('4.3.2.1', 6881)], + 'file_priorities': [1, 1], + 'http_seeds': ['http://test.com/file3'], + 'url_seeds': ['http://test.com/announce-url'], + 'peers': [('5.6.7.8', 6881)], + 'banned_peers': [('8.7.6.5', 6881)], + 'renamed_files': {0: 'test.txt', 2: 'test.txt'} + }) + self.st = self.h.status() + self.assertEqual(self.st.save_path, os.getcwd()) + trackers = self.h.trackers() + self.assertEqual(len(trackers), 1) + self.assertEqual(trackers[0].get('url'), 'http://test.com/announce') + self.assertEqual(trackers[0].get('tier'), 0) + self.assertEqual(self.h.get_file_priorities(), [1, 1]) + self.assertEqual(self.h.http_seeds(), ['http://test.com/file3']) + # url_seeds was already set, test that it did not get overwritten + self.assertEqual(self.h.url_seeds(), + ['http://test.com/announce-url/', 'http://test.com/file/']) + # piece priorities weren't set explicitly, but they were updated by the + # file priorities being set + self.assertEqual(self.h.get_piece_priorities(), [1]) + self.assertEqual(self.st.verified_pieces, []) + + +class TestAddPiece(unittest.TestCase): + + def setUp(self): + self.dir = tempfile.TemporaryDirectory() + self.session = lt.session(settings) + self.ti = lt.torrent_info(dummy_data.DICT) + self.atp = lt.add_torrent_params() + self.atp.ti = self.ti + self.atp.save_path = self.dir.name + self.handle = self.session.add_torrent(self.atp) + self.wait_for(lambda: self.handle.status().state != lt.torrent_status.checking_files + and self.handle.status().state != lt.torrent_status.checking_resume_data, msg="checking") + + def wait_for(self, condition, msg="condition", timeout=5): + deadline = time.time() + timeout + while not condition(): + self.assertLess(time.time(), deadline, msg="%s timed out" % msg) + time.sleep(0.1) + + def wait_until_torrent_finished(self): + self.wait_for(lambda: self.handle.status().progress == 1.0, msg="progress") + + def file_written(): + with open(os.path.join(self.dir.name.encode(), dummy_data.NAME), mode="rb") as f: + return f.read() == dummy_data.DATA + + self.wait_for(file_written, msg="file write") + + def test_with_str(self): + for i, data in enumerate(dummy_data.PIECES): + self.handle.add_piece(i, data.decode(), 0) + + self.wait_until_torrent_finished() + + def test_with_bytes(self): + for i, data in enumerate(dummy_data.PIECES): + self.handle.add_piece(i, data, 0) + + self.wait_until_torrent_finished() + + +class test_torrent_info(unittest.TestCase): + + def test_non_ascii_file(self): + try: + shutil.copy('base.torrent', 'base-\u745E\u5177.torrent') + except shutil.SameFileError: + pass + ti = lt.torrent_info('base-\u745E\u5177.torrent') + + self.assertTrue(len(ti.info_section()) != 0) + self.assertTrue(len(ti.hash_for_piece(0)) != 0) + + def test_bencoded_constructor(self): + # things that can be converted to a bencoded entry, will be interpreted + # as such and encoded + info = lt.torrent_info({'info': { + 'name': 'test_torrent', 'length': 1234, + 'piece length': 16 * 1024, + 'pieces': 'aaaaaaaaaaaaaaaaaaaa'}}) + + self.assertEqual(info.num_files(), 1) + + f = info.files() + self.assertEqual(f.file_path(0), 'test_torrent') + self.assertEqual(f.file_name(0), 'test_torrent') + self.assertEqual(f.file_size(0), 1234) + self.assertEqual(info.total_size(), 1234) + self.assertEqual(info.creation_date(), 0) + + def test_bytearray(self): + # a bytearray object is interpreted as a bencoded buffer + info = lt.torrent_info(bytearray(lt.bencode({'info': { + 'name': 'test_torrent', 'length': 1234, + 'piece length': 16 * 1024, + 'pieces': 'aaaaaaaaaaaaaaaaaaaa'}}))) + self.assertEqual(info.num_files(), 1) + + def test_bytes(self): + # a bytes object is interpreted as a bencoded buffer + info = lt.torrent_info(bytes(lt.bencode({'info': { + 'name': 'test_torrent', 'length': 1234, + 'piece length': 16 * 1024, + 'pieces': 'aaaaaaaaaaaaaaaaaaaa'}}))) + self.assertEqual(info.num_files(), 1) + + def test_load_decode_depth_limit(self): + self.assertRaises(RuntimeError, lambda: lt.torrent_info( + {'test': {'test': {'test': {'test': {'test': {}}}}}, 'info': { + 'name': 'test_torrent', 'length': 1234, + 'piece length': 16 * 1024, + 'pieces': 'aaaaaaaaaaaaaaaaaaaa'}}, {'max_decode_depth': 1})) + + def test_load_max_pieces_limit(self): + self.assertRaises(RuntimeError, lambda: lt.torrent_info( + {'info': { + 'name': 'test_torrent', 'length': 1234000, + 'piece length': 16 * 1024, + 'pieces': 'aaaaaaaaaaaaaaaaaaaa'}}, {'max_pieces': 1})) + + def test_load_max_buffer_size_limit(self): + self.assertRaises(RuntimeError, lambda: lt.torrent_info( + {'info': { + 'name': 'test_torrent', 'length': 1234000, + 'piece length': 16 * 1024, + 'pieces': 'aaaaaaaaaaaaaaaaaaaa'}}, {'max_buffer_size': 1})) + + def test_info_section(self): + ti = lt.torrent_info('base.torrent') + + self.assertTrue(len(ti.info_section()) != 0) + self.assertTrue(len(ti.hash_for_piece(0)) != 0) + + def test_torrent_info_bytes_overload(self): + # bytes will never be interpreted as a file name. It's interpreted as a + # bencoded buffer + with self.assertRaises(RuntimeError): + ti = lt.torrent_info(b'base.torrent') + + def test_web_seeds(self): + ti = lt.torrent_info('base.torrent') + + ws = [{'url': 'http://foo/test', 'auth': '', 'type': 0}, + {'url': 'http://bar/test', 'auth': '', 'type': 1}] + ti.set_web_seeds(ws) + web_seeds = ti.web_seeds() + self.assertEqual(len(ws), len(web_seeds)) + for i in range(len(web_seeds)): + self.assertEqual(web_seeds[i]["url"], ws[i]["url"]) + self.assertEqual(web_seeds[i]["auth"], ws[i]["auth"]) + self.assertEqual(web_seeds[i]["type"], ws[i]["type"]) + + def test_announce_entry(self): + ae = lt.announce_entry('test') + self.assertEqual(ae.url, 'test') + self.assertEqual(ae.tier, 0) + self.assertEqual(ae.verified, False) + self.assertEqual(ae.source, 0) + + def test_torrent_info_sha1_overload(self): + ti = lt.torrent_info(lt.info_hash_t(lt.sha1_hash(b'a' * 20))) + self.assertEqual(ti.info_hash(), lt.sha1_hash(b'a' * 20)) + self.assertEqual(ti.info_hashes().v1, lt.sha1_hash(b'a' * 20)) + + ti_copy = lt.torrent_info(ti) + self.assertEqual(ti_copy.info_hash(), lt.sha1_hash(b'a' * 20)) + self.assertEqual(ti_copy.info_hashes().v1, lt.sha1_hash(b'a' * 20)) + + def test_torrent_info_sha256_overload(self): + ti = lt.torrent_info(lt.info_hash_t(lt.sha256_hash(b'a' * 32))) + self.assertEqual(ti.info_hashes().v2, lt.sha256_hash(b'a' * 32)) + + ti_copy = lt.torrent_info(ti) + self.assertEqual(ti_copy.info_hashes().v2, lt.sha256_hash(b'a' * 32)) + + def test_url_seed(self): + ti = lt.torrent_info('base.torrent') + + ti.add_tracker('foobar1') + ti.add_url_seed('foobar2') + ti.add_url_seed('foobar3', 'username:password') + ti.add_url_seed('foobar4', 'username:password', []) + + seeds = ti.web_seeds() + self.assertEqual(seeds, [ + {'url': 'foobar2', 'type': 0, 'auth': ''}, + {'url': 'foobar3', 'type': 0, 'auth': 'username:password'}, + {'url': 'foobar4', 'type': 0, 'auth': 'username:password'}, + ]) + + def test_http_seed(self): + ti = lt.torrent_info('base.torrent') + + ti.add_http_seed('foobar2') + ti.add_http_seed('foobar3', 'username:password') + ti.add_http_seed('foobar4', 'username:password', []) + + seeds = ti.web_seeds() + self.assertEqual(seeds, [ + {'url': 'foobar2', 'type': 1, 'auth': ''}, + {'url': 'foobar3', 'type': 1, 'auth': 'username:password'}, + {'url': 'foobar4', 'type': 1, 'auth': 'username:password'}, + ]) + +class test_alerts(unittest.TestCase): + + def test_alert(self): + + ses = lt.session(settings) + ti = lt.torrent_info('base.torrent') + h = ses.add_torrent({'ti': ti, 'save_path': os.getcwd()}) + st = h.status() + time.sleep(1) + ses.remove_torrent(h) + ses.wait_for_alert(1000) # milliseconds + alerts = ses.pop_alerts() + for a in alerts: + if a.what() == 'add_torrent_alert': + self.assertEqual(a.torrent_name, 'temp') + print(a.message()) + for field_name in dir(a): + if field_name.startswith('__'): + continue + field = getattr(a, field_name) + if callable(field): + print(' ', field_name, ' = ', field()) + else: + print(' ', field_name, ' = ', field) + + print(st.next_announce) + self.assertEqual(st.name, 'temp') + print(st.errc.message()) + print(st.pieces) + print(st.last_seen_complete) + print(st.completed_time) + print(st.progress) + print(st.num_pieces) + print(st.distributed_copies) + print(st.info_hashes) + print(st.seeding_duration) + print(st.last_upload) + print(st.last_download) + self.assertEqual(st.save_path, os.getcwd()) + + def test_alert_fs(self): + ses = lt.session(settings) + s1, s2 = socket.socketpair() + ses.set_alert_fd(s2.fileno()) + + ses.pop_alerts() + + # make sure there's an alert to wake us up + ses.post_session_stats() + + read_sockets, write_sockets, error_sockets = select.select([s1], [], []) + + self.assertEqual(len(read_sockets), 1) + for s in read_sockets: + s.recv(10) + + def test_pop_alerts(self): + ses = lt.session(settings) + ses.async_add_torrent( + {"ti": lt.torrent_info("base.torrent"), "save_path": "."}) + +# this will cause an error (because of duplicate torrents) and the +# torrent_info object created here will be deleted once the alert goes out +# of scope. When that happens, it will decrement the python object, to allow +# it to release the object. +# we're trying to catch the error described in this post, with regards to +# torrent_info. +# https://mail.python.org/pipermail/cplusplus-sig/2007-June/012130.html + ses.async_add_torrent( + {"ti": lt.torrent_info("base.torrent"), "save_path": "."}) + time.sleep(1) + for i in range(0, 10): + alerts = ses.pop_alerts() + for a in alerts: + print(a.message()) + time.sleep(0.1) + + def test_alert_notify(self): + ses = lt.session(settings) + event = threading.Event() + + def callback(): + event.set() + + ses.set_alert_notify(callback) + ses.async_add_torrent( + {"ti": lt.torrent_info("base.torrent"), "save_path": "."}) + event.wait() + + +class test_bencoder(unittest.TestCase): + + def test_bencode(self): + encoded = lt.bencode({'a': 1, 'b': [1, 2, 3], 'c': 'foo'}) + self.assertEqual(encoded, b'd1:ai1e1:bli1ei2ei3ee1:c3:fooe') + + def test_bdecode(self): + encoded = b'd1:ai1e1:bli1ei2ei3ee1:c3:fooe' + decoded = lt.bdecode(encoded) + self.assertEqual(decoded, {b'a': 1, b'b': [1, 2, 3], b'c': b'foo'}) + + def test_string(self): + encoded = lt.bencode('foo\u00e5\u00e4\u00f6') + self.assertEqual(encoded, b'9:foo\xc3\xa5\xc3\xa4\xc3\xb6') + + def test_bytes(self): + encoded = lt.bencode(b'foo') + self.assertEqual(encoded, b'3:foo') + + def test_float(self): + # TODO: this should throw a TypeError in the future + with self.assertWarns(DeprecationWarning): + encoded = lt.bencode(1.337) + self.assertEqual(encoded, b'0:') + + def test_object(self): + class FooBar: + dummy = 1 + + # TODO: this should throw a TypeError in the future + with self.assertWarns(DeprecationWarning): + encoded = lt.bencode(FooBar()) + self.assertEqual(encoded, b'0:') + + def test_preformatted(self): + encoded = lt.bencode((1, 2, 3, 4, 5)) + self.assertEqual(encoded, b'\x01\x02\x03\x04\x05') + +class test_sha1hash(unittest.TestCase): + + def test_sha1hash(self): + h = 'a0' * 20 + s = lt.sha1_hash(binascii.unhexlify(h)) + self.assertEqual(h, str(s)) + + def test_hash(self): + self.assertNotEqual(hash(lt.sha1_hash(b'b' * 20)), hash(lt.sha1_hash(b'a' * 20))) + self.assertEqual(hash(lt.sha1_hash(b'b' * 20)), hash(lt.sha1_hash(b'b' * 20))) + +class test_sha256hash(unittest.TestCase): + + def test_sha1hash(self): + h = 'a0' * 32 + s = lt.sha256_hash(binascii.unhexlify(h)) + self.assertEqual(h, str(s)) + + def test_hash(self): + self.assertNotEqual(hash(lt.sha256_hash(b'b' * 32)), hash(lt.sha256_hash(b'a' * 32))) + self.assertEqual(hash(lt.sha256_hash(b'b' * 32)), hash(lt.sha256_hash(b'b' * 32))) + +class test_info_hash(unittest.TestCase): + + def test_info_hash(self): + s1 = lt.sha1_hash(b'a' * 20) + s2 = lt.sha256_hash(b'b' * 32) + + ih1 = lt.info_hash_t(s1); + self.assertTrue(ih1.has_v1()) + self.assertFalse(ih1.has_v2()) + self.assertEqual(ih1.v1, s1) + + ih2 = lt.info_hash_t(s2); + self.assertFalse(ih2.has_v1()) + self.assertTrue(ih2.has_v2()) + self.assertEqual(ih2.v2, s2) + + ih12 = lt.info_hash_t(s1, s2); + self.assertTrue(ih12.has_v1()) + self.assertTrue(ih12.has_v2()) + self.assertEqual(ih12.v1, s1) + self.assertEqual(ih12.v2, s2) + + self.assertNotEqual(hash(ih1), hash(ih2)) + self.assertNotEqual(hash(ih1), hash(ih12)) + self.assertEqual(hash(ih1), hash(lt.info_hash_t(s1))) + self.assertEqual(hash(ih2), hash(lt.info_hash_t(s2))) + self.assertEqual(hash(ih12), hash(lt.info_hash_t(s1, s2))) + +class test_magnet_link(unittest.TestCase): + + def test_parse_magnet_uri(self): + ses = lt.session({}) + magnet = 'magnet:?xt=urn:btih:C6EIF4CCYDBTIJVG3APAGM7M4NDONCTI' + p = lt.parse_magnet_uri(magnet) + self.assertEqual(str(p.info_hashes.v1), '178882f042c0c33426a6d81e0333ece346e68a68') + p.save_path = '.' + h = ses.add_torrent(p) + self.assertEqual(str(h.info_hash()), '178882f042c0c33426a6d81e0333ece346e68a68') + self.assertEqual(str(h.info_hashes().v1), '178882f042c0c33426a6d81e0333ece346e68a68') + + def test_parse_magnet_uri_dict(self): + ses = lt.session({}) + magnet = 'magnet:?xt=urn:btih:C6EIF4CCYDBTIJVG3APAGM7M4NDONCTI' + p = lt.parse_magnet_uri_dict(magnet) + self.assertEqual(binascii.hexlify(p['info_hashes']), b'178882f042c0c33426a6d81e0333ece346e68a68') + p['save_path'] = '.' + h = ses.add_torrent(p) + self.assertEqual(str(h.info_hash()), '178882f042c0c33426a6d81e0333ece346e68a68') + self.assertEqual(str(h.info_hashes().v1), '178882f042c0c33426a6d81e0333ece346e68a68') + + def test_add_deprecated_magnet_link(self): + ses = lt.session() + atp = lt.add_torrent_params() + atp.info_hashes = lt.info_hash_t(lt.sha1_hash(b"a" * 20)) + h = ses.add_torrent(atp) + + self.assertTrue(h.status().info_hashes == lt.info_hash_t(lt.sha1_hash(b"a" * 20))) + + def test_add_magnet_link(self): + ses = lt.session() + atp = lt.add_torrent_params() + atp.info_hash = lt.sha1_hash(b"a" * 20) + h = ses.add_torrent(atp) + + self.assertTrue(h.status().info_hashes == lt.info_hash_t(lt.sha1_hash(b"a" * 20))) + + +class test_peer_class(unittest.TestCase): + + def test_peer_class_ids(self): + s = lt.session(settings) + + print('global_peer_class_id:', lt.session.global_peer_class_id) + print('tcp_peer_class_id:', lt.session.tcp_peer_class_id) + print('local_peer_class_id:', lt.session.local_peer_class_id) + + print('global: ', s.get_peer_class(s.global_peer_class_id)) + print('tcp: ', s.get_peer_class(s.local_peer_class_id)) + print('local: ', s.get_peer_class(s.local_peer_class_id)) + + def test_peer_class(self): + s = lt.session(settings) + + c = s.create_peer_class('test class') + print('new class: ', s.get_peer_class(c)) + + nfo = s.get_peer_class(c) + self.assertEqual(nfo['download_limit'], 0) + self.assertEqual(nfo['upload_limit'], 0) + self.assertEqual(nfo['ignore_unchoke_slots'], False) + self.assertEqual(nfo['connection_limit_factor'], 100) + self.assertEqual(nfo['download_priority'], 1) + self.assertEqual(nfo['upload_priority'], 1) + self.assertEqual(nfo['label'], 'test class') + + nfo['download_limit'] = 1337 + nfo['upload_limit'] = 1338 + nfo['ignore_unchoke_slots'] = True + nfo['connection_limit_factor'] = 42 + nfo['download_priority'] = 2 + nfo['upload_priority'] = 3 + + s.set_peer_class(c, nfo) + + nfo2 = s.get_peer_class(c) + self.assertEqual(nfo, nfo2) + + def test_peer_class_filter(self): + filt = lt.peer_class_type_filter() + filt.add(lt.peer_class_type_filter.tcp_socket, lt.session.global_peer_class_id) + filt.remove(lt.peer_class_type_filter.utp_socket, lt.session.local_peer_class_id) + + filt.disallow(lt.peer_class_type_filter.tcp_socket, lt.session.global_peer_class_id) + filt.allow(lt.peer_class_type_filter.utp_socket, lt.session.local_peer_class_id) + + def test_peer_class_ip_filter(self): + s = lt.session(settings) + s.set_peer_class_type_filter(lt.peer_class_type_filter()) + s.set_peer_class_filter(lt.ip_filter()) + +class test_ip_filter(unittest.TestCase): + + def test_export(self): + + f = lt.ip_filter() + self.assertEqual(f.access('1.1.1.1'), 0) + f.add_rule('1.1.1.1', '1.1.1.2', 1) + self.assertEqual(f.access('1.1.1.0'), 0) + self.assertEqual(f.access('1.1.1.1'), 1) + self.assertEqual(f.access('1.1.1.2'), 1) + self.assertEqual(f.access('1.1.1.3'), 0) + exp = f.export_filter() + self.assertEqual(exp, ([('0.0.0.0', '1.1.1.0'), ('1.1.1.1', '1.1.1.2'), ('1.1.1.3', '255.255.255.255')], [('::', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')])) + +class test_session(unittest.TestCase): + + def test_settings(self): + sett = { 'alert_mask': lt.alert.category_t.all_categories } + s = lt.session(sett) + sett = s.get_settings() + self.assertEqual(sett['alert_mask'] & 0x7fffffff, 0x7fffffff) + + def test_session_params(self): + sp = lt.session_params() + sp.settings = { 'alert_mask': lt.alert.category_t.all_categories } + s = lt.session(sp) + sett = s.get_settings() + self.assertEqual(sett['alert_mask'] & 0x7fffffff, 0x7fffffff) + + def test_session_params_constructor(self): + sp = lt.session_params({ 'alert_mask': lt.alert.category_t.all_categories }) + s = lt.session(sp) + sett = s.get_settings() + self.assertEqual(sett['alert_mask'] & 0x7fffffff, 0x7fffffff) + + def test_session_params_ip_filter(self): + sp = lt.session_params() + sp.ip_filter.add_rule("1.1.1.1", "1.1.1.2", 1337) + self.assertEqual(sp.ip_filter.access("1.1.1.1"), 1337) + self.assertEqual(sp.ip_filter.access("1.1.1.2"), 1337) + self.assertEqual(sp.ip_filter.access("1.1.1.3"), 0) + + def test_session_params_roundtrip_buf(self): + + sp = lt.session_params() + sp.settings = { 'alert_mask': lt.alert.category_t.all_categories } + + buf = lt.write_session_params_buf(sp) + sp2 = lt.read_session_params(buf) + self.assertEqual(sp2.settings['alert_mask'] & 0x7fffffff, 0x7fffffff) + + def test_session_params_roundtrip_entry(self): + + sp = lt.session_params() + sp.settings = { 'alert_mask': lt.alert.category_t.all_categories } + + ent = lt.write_session_params(sp) + print(ent) + sp2 = lt.read_session_params(ent) + self.assertEqual(sp2.settings['alert_mask'] & 0x7fffffff, 0x7fffffff) + + def test_add_torrent(self): + s = lt.session(settings) + h = s.add_torrent({'ti': lt.torrent_info('base.torrent'), + 'save_path': '.', + 'dht_nodes': [('1.2.3.4', 6881), ('4.3.2.1', 6881)], + 'http_seeds': ['http://test.com/seed'], + 'peers': [('5.6.7.8', 6881)], + 'banned_peers': [('8.7.6.5', 6881)], + 'file_priorities': [1, 1, 1, 2, 0]}) + + def test_find_torrent(self): + s = lt.session(settings) + h = s.add_torrent({'info_hash': b"a" * 20, + 'save_path': '.'}) + self.assertTrue(h.is_valid()) + + h2 = s.find_torrent(lt.sha1_hash(b"a" * 20)) + self.assertTrue(h2.is_valid()) + h3 = s.find_torrent(lt.sha1_hash(b"b" * 20)) + self.assertFalse(h3.is_valid()) + + self.assertEqual(h, h2) + self.assertNotEqual(h, h3) + + def test_add_torrent_info_hash(self): + s = lt.session(settings) + h = s.add_torrent({ + 'info_hash': b'a' * 20, + 'info_hashes': b'a' * 32, + 'save_path': '.'}) + + time.sleep(1) + alerts = s.pop_alerts() + + while len(alerts) > 0: + a = alerts.pop(0) + print(a) + + self.assertTrue(h.is_valid()) + self.assertEqual(h.status().info_hashes, lt.info_hash_t(lt.sha256_hash(b'a' * 32))) + + def test_session_status(self): + if not has_deprecated(): + return + + s = lt.session() + st = s.status() + print(st) + print(st.active_requests) + print(st.dht_nodes) + print(st.dht_node_cache) + print(st.dht_torrents) + print(st.dht_global_nodes) + print(st.dht_total_allocations) + + def test_apply_settings(self): + + s = lt.session(settings) + s.apply_settings({'num_want': 66, 'user_agent': 'test123'}) + self.assertEqual(s.get_settings()['num_want'], 66) + self.assertEqual(s.get_settings()['user_agent'], 'test123') + + def test_post_session_stats(self): + s = lt.session({'alert_mask': 0, 'enable_dht': False}) + s.post_session_stats() + alerts = [] + # first the stats headers log line. but not if logging is disabled + while len(alerts) == 0: + s.wait_for_alert(1000) + alerts = s.pop_alerts() + + while len(alerts) > 0: + a = alerts.pop(0) + print(a) + if isinstance(a, lt.session_stats_header_alert): + break + self.assertTrue(isinstance(a, lt.session_stats_header_alert)) + # then the actual stats values + while len(alerts) == 0: + s.wait_for_alert(1000) + alerts = s.pop_alerts() + a = alerts.pop(0) + print(a) + self.assertTrue(isinstance(a, lt.session_stats_alert)) + self.assertTrue(isinstance(a.values, dict)) + self.assertTrue(len(a.values) > 0) + + def test_post_dht_stats(self): + s = lt.session({'alert_mask': 0, 'enable_dht': False}) + s.post_dht_stats() + alerts = [] + cnt = 0 + while len(alerts) == 0: + s.wait_for_alert(1000) + alerts = s.pop_alerts() + cnt += 1 + if cnt > 60: + print('no dht_stats_alert in 1 minute!') + sys.exit(1) + a = alerts.pop(0) + self.assertTrue(isinstance(a, lt.dht_stats_alert)) + self.assertTrue(isinstance(a.active_requests, list)) + self.assertTrue(isinstance(a.routing_table, list)) + + def test_unknown_settings(self): + try: + lt.session({'unexpected-key-name': 42}) + self.assertFalse('should have thrown an exception') + except KeyError as e: + print(e) + + def test_fingerprint(self): + self.assertEqual(lt.generate_fingerprint('LT', 0, 1, 2, 3), '-LT0123-') + self.assertEqual(lt.generate_fingerprint('..', 10, 1, 2, 3), '-..A123-') + + def test_min_memory_preset(self): + min_mem = lt.min_memory_usage() + print(min_mem) + + self.assertTrue('allow_idna' not in min_mem) + self.assertTrue('connection_speed' in min_mem) + self.assertTrue('file_pool_size' in min_mem) + + def test_seed_mode_preset(self): + seed_mode = lt.high_performance_seed() + print(seed_mode) + + self.assertTrue('alert_queue_size' in seed_mode) + self.assertTrue('connection_speed' in seed_mode) + self.assertTrue('file_pool_size' in seed_mode) + + def test_default_settings(self): + + default = lt.default_settings() + print(default) + + +class test_example_client(unittest.TestCase): + + def test_execute_client(self): + if os.name == 'nt': + # TODO: fix windows includes of client.py + return + my_stdin = sys.stdin + if os.name != 'nt': + master_fd, slave_fd = pty.openpty() + # slave_fd fix multiple stdin assignment at termios.tcgetattr + my_stdin = slave_fd + + process = sub.Popen( + [sys.executable, "client.py", "url_seed_multi.torrent"], + stdin=my_stdin, stdout=sub.PIPE, stderr=sub.PIPE) + # python2 has no Popen.wait() timeout + time.sleep(5) + returncode = process.poll() + if returncode is None: + # this is an expected use-case + process.kill() + err = process.stderr.read().decode("utf-8") + self.assertEqual('', err, 'process throw errors: \n' + err) + # check error code if process did unexpected end + if returncode is not None: + # in case of error return: output stdout if nothing was on stderr + if returncode != 0: + print("stdout:\n" + process.stdout.read().decode("utf-8")) + self.assertEqual(returncode, 0, "returncode: " + str(returncode) + "\n" + + "stderr: empty\n" + + "some configuration does not output errors like missing module members," + + "try to call it manually to get the error message\n") + + def test_execute_simple_client(self): + process = sub.Popen( + [sys.executable, "simple_client.py", "url_seed_multi.torrent"], + stdout=sub.PIPE, stderr=sub.PIPE) + # python2 has no Popen.wait() timeout + time.sleep(5) + returncode = process.poll() + if returncode is None: + # this is an expected use-case + process.kill() + err = process.stderr.read().decode("utf-8") + self.assertEqual('', err, 'process throw errors: \n' + err) + # check error code if process did unexpected end + if returncode is not None: + # in case of error return: output stdout if nothing was on stderr + if returncode != 0: + print("stdout:\n" + process.stdout.read().decode("utf-8")) + self.assertEqual(returncode, 0, "returncode: " + str(returncode) + "\n" + + "stderr: empty\n" + + "some configuration does not output errors like missing module members," + + "try to call it manually to get the error message\n") + + def test_execute_make_torrent(self): + process = sub.Popen( + [sys.executable, "make_torrent.py", "url_seed_multi.torrent", + "http://test.com/test"], stdout=sub.PIPE, stderr=sub.PIPE) + returncode = process.wait() + # python2 has no Popen.wait() timeout + err = process.stderr.read().decode("utf-8") + self.assertEqual('', err, 'process throw errors: \n' + err) + # in case of error return: output stdout if nothing was on stderr + if returncode != 0: + print("stdout:\n" + process.stdout.read().decode("utf-8")) + self.assertEqual(returncode, 0, "returncode: " + str(returncode) + "\n" + + "stderr: empty\n" + + "some configuration does not output errors like missing module members," + + "try to call it manually to get the error message\n") + + def test_default_settings(self): + + default = lt.default_settings() + self.assertNotIn('', default) + print(default) + + +class test_operation_t(unittest.TestCase): + + def test_enum(self): + self.assertEqual(lt.operation_name(lt.operation_t.sock_accept), "sock_accept") + self.assertEqual(lt.operation_name(lt.operation_t.unknown), "unknown") + self.assertEqual(lt.operation_name(lt.operation_t.mkdir), "mkdir") + self.assertEqual(lt.operation_name(lt.operation_t.partfile_write), "partfile_write") + self.assertEqual(lt.operation_name(lt.operation_t.hostname_lookup), "hostname_lookup") + + +class test_error_code(unittest.TestCase): + + def test_error_code(self): + + a = lt.error_code() + a = lt.error_code(10, lt.libtorrent_category()) + self.assertEqual(a.category().name(), 'libtorrent') + + self.assertEqual(lt.libtorrent_category().name(), 'libtorrent') + self.assertEqual(lt.upnp_category().name(), 'upnp') + self.assertEqual(lt.http_category().name(), 'http') + self.assertEqual(lt.socks_category().name(), 'socks') + self.assertEqual(lt.bdecode_category().name(), 'bdecode') + self.assertEqual(lt.generic_category().name(), 'generic') + self.assertEqual(lt.system_category().name(), 'system') + + +class test_peer_info(unittest.TestCase): + + def test_peer_info_members(self): + + p = lt.peer_info() + + print(p.client) + print(p.pieces) + print(p.pieces) + print(p.last_request) + print(p.last_active) + print(p.flags) + print(p.source) + print(p.pid) + print(p.downloading_piece_index) + print(p.ip) + print(p.local_endpoint) + print(p.read_state) + print(p.write_state) + + +class test_dht_settings(unittest.TestCase): + + def test_dht_get_peers(self): + session = lt.session(); + info_hash = lt.sha1_hash(b"a" * 20) + session.dht_get_peers(info_hash); + + def test_construct(self): + + ds = lt.dht_settings() + + print(ds.max_peers_reply) + print(ds.search_branching) + print(ds.max_fail_count) + print(ds.max_fail_count) + print(ds.max_torrents) + print(ds.max_dht_items) + print(ds.restrict_routing_ips) + print(ds.restrict_search_ips) + print(ds.max_torrent_search_reply) + print(ds.extended_routing_table) + print(ds.aggressive_lookups) + print(ds.privacy_lookups) + print(ds.enforce_node_id) + print(ds.ignore_dark_internet) + print(ds.block_timeout) + print(ds.block_ratelimit) + print(ds.read_only) + print(ds.item_lifetime) + + +def get_isolated_settings(): + return { + "enable_dht": False, + "enable_lsd": False, + "enable_natpmp": False, + "enable_upnp": False, + "listen_interfaces": "127.0.0.1:0", + "dht_bootstrap_nodes": "", + } + + +def loop_until_timeout(timeout, msg="condition"): + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + yield + raise AssertionError(f"{msg} timed out") + + +def unlink_all_files(path): + for dirpath, _, filenames in os.walk(path): + for filename in filenames: + filepath = os.path.join(dirpath, filename) + os.unlink(filepath) + + +# In test cases where libtorrent writes torrent data in a temporary directory, +# cleaning up the tempdir on Windows CI sometimes fails with a PermissionError +# having WinError 5 (Access Denied). I can't repro this WinError in any way; +# holding an open file handle results in a different WinError. Seems to be a +# race condition which only happens with very short-lived tests which write +# data. Work around by cleaning up the tempdir in a loop. + +# TODO: why is this necessary? +def cleanup_with_windows_fix(tempdir, *, timeout): + # Clean up just the files, so we don't have to bother with depth-first + # traversal + for _ in loop_until_timeout(timeout, msg="PermissionError clear"): + try: + unlink_all_files(tempdir.name) + except PermissionError: + if sys.platform == "win32": + # current release of mypy doesn't know about winerror + # if exc.winerror == 5: + continue + raise + break + # This removes directories in depth-first traversal. + # It also marks the tempdir as explicitly cleaned so it doesn't trigger a + # ResourceWarning. + tempdir.cleanup() + + +def wait_for(session, alert_type, *, timeout, prefix=None): + # Return the first alert of type, but log all alerts. + result = None + for _ in loop_until_timeout(timeout, msg=alert_type.__name__): + for alert in session.pop_alerts(): + print(f"{alert.what()}: {alert.message()}") + if result is None and isinstance(alert, alert_type): + result = alert + if result is not None: + return result + raise AssertionError("unreachable") + + +class LambdaRequestHandler(http.server.BaseHTTPRequestHandler): + default_request_version = "HTTP/1.1" + + def __init__(self, get_data, *args, **kwargs): + self.get_data = get_data + super().__init__(*args, **kwargs) + + def do_GET(self): + print(f"mock tracker request: {self.requestline}") + data = self.get_data() + self.send_response(200) + self.send_header("Content-Type", "application/octet-stream") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + + +class SSLTrackerAlertTest(unittest.TestCase): + + def setUp(self): + self.cert_path = os.path.realpath(os.path.join( + os.path.dirname(__file__), "..", "..", "test", "ssl", "server.pem" + )) + print(f"cert_path = {self.cert_path}") + + self.tracker_response = { + b"external ip": b"\x01\x02\x03\x04", + } + self.tracker = http.server.HTTPServer( + ("127.0.0.1", 0), + functools.partial( + LambdaRequestHandler, lambda: lt.bencode(self.tracker_response) + ), + ) + self.ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + self.ctx.load_cert_chain(self.cert_path) + self.tracker.socket = self.ctx.wrap_socket( + self.tracker.socket, server_side=True + ) + self.tracker_thread = threading.Thread(target=self.tracker.serve_forever) + self.tracker_thread.start() + # HTTPServer.server_name seems to resolve to things like + # "localhost.localdomain" + port = self.tracker.server_port + self.tracker_url = f"https://127.0.0.1:{port}/announce" + print(f"mock tracker url = {self.tracker_url}") + + self.settings = get_isolated_settings() + self.settings["alert_mask"] = lt.alert_category.status + # I couldn't get validation to work on all platforms. Setting + # SSL_CERT_FILE to our self-signed cert works on linux and mac, but + # not on Windows. + self.settings["validate_https_trackers"] = False + self.session = lt.session(self.settings) + self.dir = tempfile.TemporaryDirectory() + self.atp = lt.add_torrent_params() + self.atp.info_hash = dummy_data.get_sha1_hash() + self.atp.flags &= ~lt.torrent_flags.auto_managed + self.atp.flags &= ~lt.torrent_flags.paused + self.atp.save_path = self.dir.name + + def tearDown(self): + # we do this because sessions writing data can collide with + # cleaning up temporary directories. session.abort() isn't bound + handles = self.session.get_torrents() + for handle in handles: + self.session.remove_torrent(handle) + for _ in loop_until_timeout(5, msg="clear all handles"): + if not any(handle.is_valid() for handle in handles): + break + cleanup_with_windows_fix(self.dir, timeout=5) + self.tracker.shutdown() + # Explicitly clean up server sockets, to avoid ResourceWarning + self.tracker.server_close() + + def test_external_ip_alert_via_ssl_tracker(self): + handle = self.session.add_torrent(self.atp) + handle.add_tracker({"url": self.tracker_url}) + + alert = wait_for(self.session, lt.external_ip_alert, timeout=60) + + self.assertEqual(alert.category(), lt.alert_category.status) + self.assertEqual(alert.what(), "external_ip") + self.assertIsInstance(alert.message(), str) + self.assertNotEqual(alert.message(), "") + self.assertEqual(str(alert), alert.message()) + self.assertEqual(alert.external_address, "1.2.3.4") + + +if __name__ == '__main__': + print(lt.__version__) + try: + shutil.copy(os.path.join('..', '..', 'test', 'test_torrents', + 'url_seed_multi.torrent'), '.') + except shutil.SameFileError: + pass + try: + shutil.copy(os.path.join('..', '..', 'test', 'test_torrents', + 'base.torrent'), '.') + except shutil.SameFileError: + pass + try: + shutil.copy(os.path.join('..', '..', 'test', 'test_torrents', + 'unordered.torrent'), '.') + except shutil.SameFileError: + pass + unittest.main() diff --git a/clang_tidy.jam b/clang_tidy.jam new file mode 100644 index 0000000..b80948b --- /dev/null +++ b/clang_tidy.jam @@ -0,0 +1,105 @@ +# Copyright (c) 2018 Arvid Norberg (arvid@libtorrent.org) +# +# Use, modification and distribution is subject to the Boost Software +# License Version 1.0. (See accompanying file LICENSE_1_0.txt or +# http://www.boost.org/LICENSE_1_0.txt) + +import common ; +import toolset ; +import feature ; + +feature.extend toolset : clang_tidy ; + +generators.register-c-compiler clang_tidy.compile.c++ : CPP : OBJ : clang_tidy ; +generators.register-c-compiler clang_tidy.compile.c : C : OBJ : clang_tidy ; +generators.register-archiver clang_tidy.archive : OBJ : STATIC_LIB : clang_tidy ; +generators.register-linker clang_tidy.link : OBJ SEARCHED_LIB STATIC_LIB : EXE : clang_tidy ; +generators.register-linker clang_tidy.link.dll : OBJ SEARCHED_LIB STATIC_LIB : SHARED_LIB : clang_tidy ; + +rule init ( version ? : command * : options * ) { + command = [ common.get-invocation-command clang_tidy : clang-tidy + : $(command) ] ; + + # Determine the version + if $(command) { + local command-string = \"$(command)\" ; + command-string = $(command-string:J=" ") ; + version ?= [ MATCH "version ([0-9.]+)" + : [ SHELL "$(command-string) --version" ] ] ; + } + + local condition = [ common.check-init-parameters clang_tidy + : version $(version) ] ; + + common.handle-options clang_tidy : $(condition) : $(command) : $(options) ; +} + +############################################################################### +# Flags + +toolset.flags clang_tidy.compile OPTIONS ; +toolset.flags clang_tidy.compile.c++ OPTIONS ; + +toolset.flags clang_tidy.compile DEFINES ; +toolset.flags clang_tidy.compile INCLUDES ; + +toolset.flags clang_tidy.compile OPTIONS off : ; +toolset.flags clang_tidy.compile OPTIONS speed : -O3 ; +toolset.flags clang_tidy.compile OPTIONS space : -Os ; + +toolset.flags clang_tidy.compile OPTIONS off : -fno-inline ; +# For clang, 'on' and 'full' are identical. +toolset.flags clang_tidy.compile OPTIONS on : -Wno-inline ; +toolset.flags clang_tidy.compile OPTIONS full : -Wno-inline ; + +toolset.flags clang_tidy.compile OPTIONS off : -w ; +toolset.flags clang_tidy.compile OPTIONS on : -Wall ; +toolset.flags clang_tidy.compile OPTIONS all : -Wall -pedantic ; +toolset.flags clang_tidy.compile OPTIONS on : -Werror ; + +toolset.flags clang_tidy.compile OPTIONS on : -g ; +toolset.flags clang_tidy.compile OPTIONS on : -pg ; +toolset.flags clang_tidy.compile OPTIONS off : -fno-rtti ; + +local rule cxxstd-flags ( toolset : condition * : options * ) +{ + toolset.flags $(toolset).compile.c++ OPTIONS $(condition) : $(options) : unchecked ; + toolset.flags $(toolset).link OPTIONS $(condition) : $(options) : unchecked ; +} + +cxxstd-flags clang_tidy : 11/iso : -std=c++11 ; +cxxstd-flags clang_tidy : 11/gnu : -std=gnu++11 ; +cxxstd-flags clang_tidy : 14/iso : -std=c++14 ; +cxxstd-flags clang_tidy : 14/gnu : -std=gnu++14 ; +cxxstd-flags clang_tidy : 17/iso : -std=c++17 ; +cxxstd-flags clang_tidy : 17/gnu : -std=gnu++17 ; + +############################################################################### +# C and C++ compilation + +TOUCH = [ common.file-creation-command ] ; + +actions compile.c++ { + "$(CONFIG_COMMAND)" -quiet -header-filter=* -warnings-as-errors=* "$(>)" -- -x c++ $(OPTIONS) -D$(DEFINES) -I"$(INCLUDES)" \ + && $(TOUCH) "$(<)" +} + +actions compile.c { + "$(CONFIG_COMMAND)" -quiet -header-filter=* -warnings-as-errors=* "$(>)" -- -x c $(OPTIONS) -D$(DEFINES) -I"$(INCLUDES)" \ + && $(TOUCH) "$(<)" +} + +############################################################################### +# Linking + +actions archive { + $(TOUCH) "$(<)" +} + +actions link { + $(TOUCH) "$(<)" +} + +actions link.dll { + $(TOUCH) "$(<)" +} 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..1bb8dc0 --- /dev/null +++ b/cmake/Modules/GeneratePkgConfig.cmake @@ -0,0 +1,183 @@ +# 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 "$" "" _interface_compile_options "${_interface_compile_options}") + endif() + + # put target and project properties into a file + configure_file("${_GeneratePkGConfigDir}/target-compile-settings.cmake.in" + "${_generate_target_dir}/compile-settings.cmake" @ONLY) + + get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if (NOT _isMultiConfig) + set(_variables_file_name "${_generate_target_dir}/compile-settings-expanded.cmake") + + file(GENERATE OUTPUT "${_variables_file_name}" INPUT "${_generate_target_dir}/compile-settings.cmake" ${_target_arg}) + + configure_file("${_GeneratePkGConfigDir}/generate-pkg-config.cmake.in" + "${_generate_target_dir}/generate-pkg-config.cmake" @ONLY) + + install(SCRIPT "${_generate_target_dir}/generate-pkg-config.cmake") + else() + foreach(cfg IN LISTS CMAKE_CONFIGURATION_TYPES) + set(_variables_file_name "${_generate_target_dir}/${cfg}/compile-settings-expanded.cmake") + + file(GENERATE OUTPUT "${_variables_file_name}" INPUT "${_generate_target_dir}/compile-settings.cmake" CONDITION "$" ${_target_arg}) + + 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..6675718 --- /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_FULL_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/deps/asio-gnutls/Jamfile b/deps/asio-gnutls/Jamfile new file mode 100644 index 0000000..7b8c575 --- /dev/null +++ b/deps/asio-gnutls/Jamfile @@ -0,0 +1,6 @@ +alias asio-gnutls + : # no sources + : # no build requirements + : # no default build + : ./include ; + diff --git a/deps/asio-gnutls/LICENSE_1_0.txt b/deps/asio-gnutls/LICENSE_1_0.txt new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/deps/asio-gnutls/LICENSE_1_0.txt @@ -0,0 +1,23 @@ +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/deps/asio-gnutls/README.md b/deps/asio-gnutls/README.md new file mode 100644 index 0000000..28db2f9 --- /dev/null +++ b/deps/asio-gnutls/README.md @@ -0,0 +1,17 @@ +# boost-asio-gnutls +GnuTLS wrapper for Boost.Asio + +## Usage + +Add `include` as include directory for your project, then include the header `boost/asio/gnutls.hpp`. +Don't forget to link against GnuTLS instead of OpenSSL. + +The two classes `context` and `stream` in `boost::asio::gnutls` mimic the ones in `boost::asio::ssl`. + +## Test + +From the boost root directory, run: +``` +b2 [PATH_TO_THIS_REPOSITORY]/test/gnutls +``` + diff --git a/deps/asio-gnutls/include/boost/asio/gnutls.hpp b/deps/asio-gnutls/include/boost/asio/gnutls.hpp new file mode 100644 index 0000000..11fb480 --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls.hpp @@ -0,0 +1,23 @@ +// +// gnutls.hpp +// ~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_HPP +#define BOOST_ASIO_GNUTLS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#endif // BOOST_ASIO_GNUTLS_HPP diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/context.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/context.hpp new file mode 100644 index 0000000..6582312 --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/context.hpp @@ -0,0 +1,404 @@ +// +// gnutls/context.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_CONTEXT_HPP +#define BOOST_ASIO_GNUTLS_CONTEXT_HPP + +#include "context_base.hpp" +#include "error.hpp" +#include "verify_context.hpp" + +#include +#include + +#ifndef BOOST_NO_EXCEPTIONS +#include +#endif + +#include + +#include +#include +#include +#include +#include + +namespace boost { +namespace asio { +namespace gnutls { + +class stream_base; +template class stream; + +using const_buffer = boost::asio::const_buffer; + +class context : public context_base +{ +public: + enum password_purpose + { + for_reading, + for_writing + }; + + explicit context(method m) + : m_impl(std::make_shared(this, m)) + {} + context(context&& other) { *this = std::move(other); } + context(const context& other) = delete; + ~context() + { + if (m_impl) m_impl->parent = nullptr; + } + + context& operator=(context&& other) + { + m_impl = std::move(other.m_impl); + m_impl->parent = this; + return *this; + } + + native_handle_type native_handle() { return m_impl->cred; } + +#ifndef BOOST_NO_EXCEPTIONS + void set_options(options opts) + { + error_code ec; + set_options(opts, ec); + } +#endif + + error_code set_options(options opts, error_code& ec) + { + m_impl->opts |= opts; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void clear_options() + { + error_code ec; + clear_options(ec); + } +#endif + + void clear_options(error_code& ec) { m_impl->opts = 0; } + +#ifndef BOOST_NO_EXCEPTIONS + void set_default_verify_paths() + { + error_code ec; + set_default_verify_paths(ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code set_default_verify_paths(error_code& ec) + { + // Returns the number of certificates processed + int ret = gnutls_certificate_set_x509_system_trust(m_impl->cred); + if (ret < 0) ec = error_code(ret, error::get_ssl_category()); + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void set_verify_mode(verify_mode v) + { + error_code ec; + set_verify_mode(v, ec); + } +#endif + + error_code set_verify_mode(verify_mode v, error_code& ec) + { + m_impl->verify = v; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void set_verify_depth(int depth) + { + error_code ec; + set_verify_depth(depth, ec); + } +#endif + + error_code set_verify_depth(int depth, error_code& ec) + { + unsigned int const max_bits = 8200; // default + unsigned int const max_depth = static_cast(depth); + gnutls_certificate_set_verify_limits(m_impl->cred, max_bits, max_depth); + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + template void set_verify_callback(VerifyCallback callback) + { + error_code ec; + set_verify_callback(callback, ec); + } +#endif + + template + error_code set_verify_callback(VerifyCallback callback, error_code& ec) + { + m_impl->verify_callback = callback; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + template void set_password_callback(PasswordCallback callback) + { + error_code ec; + set_password_callback(callback, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + template + error_code set_password_callback(PasswordCallback callback, error_code& ec) + { + m_impl->password_callback = callback; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void use_certificate_file(std::string const& filename, file_format format) + { + error_code ec; + use_certificate_file(filename, format, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code use_certificate_file(std::string const& filename, file_format, error_code& ec) + { + m_impl->certificate_file = filename; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void use_private_key_file(std::string const& filename, file_format format) + { + error_code ec; + use_private_key_file(filename, format, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code use_private_key_file(std::string const& filename, file_format format, error_code& ec) + { + if (m_impl->certificate_file.empty()) + return ec = boost::asio::error::operation_not_supported; + + std::size_t const max_len = 256; + std::string pass; + if (m_impl->password_callback) pass = m_impl->password_callback(max_len, for_reading); + + m_impl->private_key_file = filename; + int ret = gnutls_certificate_set_x509_key_file2(m_impl->cred, + m_impl->certificate_file.c_str(), + m_impl->private_key_file.c_str(), + format == pem ? GNUTLS_X509_FMT_PEM + : GNUTLS_X509_FMT_DER, + pass.c_str(), + 0); + if (ret != GNUTLS_E_SUCCESS) ec = error_code(ret, error::get_ssl_category()); + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void use_tmp_dh_file(std::string const& filename) + { + error_code ec; + use_tmp_dh_file(filename, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code use_tmp_dh_file(std::string const&, error_code& ec) + { + // Unnecessary and discouraged on GnuTLS 3.6.0 or later. + // Since 3.6.0, DH parameters are negotiated following RFC7919. + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void use_certificate(const_buffer const& certificate, file_format format) + { + error_code ec; + use_certificate(certificate, format, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code use_certificate(const_buffer const& certificate, file_format, error_code& ec) + { + m_impl->certificate.assign(static_cast(certificate.data()), + certificate.size()); + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void use_private_key(const_buffer const& private_key, file_format format) + { + error_code ec; + use_private_key(private_key, format, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code use_private_key(const_buffer const& private_key, file_format format, error_code& ec) + { + if (m_impl->certificate.empty()) return ec = boost::asio::error::operation_not_supported; + + m_impl->private_key.assign(reinterpret_cast(private_key.data()), + private_key.size()); + + gnutls_datum_t cert; + cert.data = reinterpret_cast( + const_cast(m_impl->certificate.c_str())); // must be null terminated + cert.size = m_impl->certificate.size(); + + gnutls_datum_t key; + key.data = reinterpret_cast( + const_cast(m_impl->private_key.c_str())); // must be null terminated + key.size = m_impl->private_key.size(); + + int ret = gnutls_certificate_set_x509_key_mem2(m_impl->cred, + &cert, + &key, + format == pem ? GNUTLS_X509_FMT_PEM + : GNUTLS_X509_FMT_DER, + m_impl->passphrase.c_str(), + 0); + if (ret != GNUTLS_E_SUCCESS) ec = error_code(ret, error::get_ssl_category()); + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void load_verify_file(std::string const& ca_file) + { + error_code ec; + load_verify_file(ca_file, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + void load_verify_file(std::string const& ca_file, error_code& ec) + { + int ret = gnutls_certificate_set_x509_trust_file(m_impl->cred, + ca_file.c_str(), + GNUTLS_X509_FMT_PEM); + if (ret < 0) ec = error_code(ret, error::get_ssl_category()); + } + +#ifndef BOOST_NO_EXCEPTIONS + void add_verify_path(std::string const& dir) + { + error_code ec; + add_verify_path(dir, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + void add_verify_path(std::string const& dir, error_code& ec) + { + int ret = gnutls_certificate_set_x509_trust_dir(m_impl->cred, + dir.c_str(), + GNUTLS_X509_FMT_PEM); + if (ret < 0) ec = error_code(ret, error::get_ssl_category()); + } + +#ifndef BOOST_NO_EXCEPTIONS + void use_tmp_dh(const_buffer const& dh) + { + error_code ec; + use_tmp_dh(dh, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code use_tmp_dh(const_buffer const&, error_code& ec) + { + // Unnecessary and discouraged on GnuTLS 3.6.0 or later. + // Since 3.6.0, DH parameters are negotiated following RFC7919. + return ec; + } + + // ---------- SNI extension ---------- + +#ifndef BOOST_NO_EXCEPTIONS + void set_server_name_callback(std::function cb) + { + error_code ec; + set_server_name_callback(cb, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } + +#endif + + error_code set_server_name_callback(std::function cb, + error_code& ec) + { + m_impl->server_name_callback = std::move(cb); + return ec; + } + + // ----------------------------------- + +private: + struct impl + { + impl(context* p_, method m_) + : m(m_) + , parent(p_) + { + int ret = gnutls_certificate_allocate_credentials(&cred); + if (ret != GNUTLS_E_SUCCESS) + throw std::runtime_error("gnutls_certificate_allocate_credentials failed: " + + std::string(gnutls_strerror(ret))); + + gnutls_certificate_set_known_dh_params(cred, GNUTLS_SEC_PARAM_MEDIUM); + } + ~impl() { gnutls_certificate_free_credentials(cred); } + + bool is_server() const { return (static_cast(m) & 0x2) != 0; } + unsigned int tls_version() const { return static_cast(m) >> 16; } + + const method m; + context* parent; + + gnutls_certificate_credentials_t cred; + verify_mode verify = 0; + options opts = 0; + + std::string certificate_file, private_key_file; + std::string certificate, private_key; + std::string passphrase; + + std::function verify_callback; + std::function password_callback; + std::function server_name_callback; + }; + + std::shared_ptr m_impl; + + friend class stream_base; + template friend class stream; +}; + +} // namespace gnutls +} // namespace asio +} // namespace boost + +#include + +#endif diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/context_base.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/context_base.hpp new file mode 100644 index 0000000..c4e90d1 --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/context_base.hpp @@ -0,0 +1,92 @@ +// +// gnutls/context_base.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_CONTEXT_BASE_HPP +#define BOOST_ASIO_GNUTLS_CONTEXT_BASE_HPP + +#include +#include + +#include + +namespace boost { +namespace asio { +namespace gnutls { + +class stream_base; +template class stream; + +typedef int verify_mode; + +BOOST_ASIO_STATIC_CONSTANT(int, verify_none = 0x00); +BOOST_ASIO_STATIC_CONSTANT(int, verify_peer = 0x01); +BOOST_ASIO_STATIC_CONSTANT(int, verify_fail_if_no_peer_cert = 0x02); +BOOST_ASIO_STATIC_CONSTANT(int, verify_client_once = 0x04); // Ignored + +class context_base +{ +public: + using error_code = boost::system::error_code; + using native_handle_type = gnutls_certificate_credentials_t; + + enum method : int + { + // Any TLS version + tls = 0x0000, + tls_client = 0x0001, + tls_server = 0x0002, + + // Force specific TLS version + tlsv1 = 0x1000, // 0xXY.. => TLS X.Y + tlsv1_client = 0x1001, + tlsv1_server = 0x1002, + tlsv11 = 0x1100, + tlsv11_client = 0x1101, + tlsv11_server = 0x1102, + tlsv12 = 0x1200, + tlsv12_client = 0x1201, + tlsv12_server = 0x1202, + tlsv13 = 0x1300, + tlsv13_client = 0x1301, + tlsv13_server = 0x1302, + + // SSLv3 + TLS (for compatibility only) + sslv23 = 0x0300, + sslv23_client = 0x0301, + sslv23_server = 0x0302, + }; + + enum file_format + { + pem, + der + }; + + typedef long options; + + BOOST_ASIO_STATIC_CONSTANT(long, default_workarounds = 0x01); + BOOST_ASIO_STATIC_CONSTANT(long, single_dh_use = 0x02); // Ignored + BOOST_ASIO_STATIC_CONSTANT(long, no_sslv2 = 0x04); // Ignored, always disabled + BOOST_ASIO_STATIC_CONSTANT(long, no_sslv3 = 0x08); + + using verify_mode = gnutls::verify_mode; + + BOOST_ASIO_STATIC_CONSTANT(int, verify_none = gnutls::verify_none); + BOOST_ASIO_STATIC_CONSTANT(int, verify_peer = gnutls::verify_peer); + BOOST_ASIO_STATIC_CONSTANT(int, + verify_fail_if_no_peer_cert = gnutls::verify_fail_if_no_peer_cert); + BOOST_ASIO_STATIC_CONSTANT(int, verify_client_once = gnutls::verify_client_once); +}; + +} // namespace gnutls +} // namespace asio +} // namespace boost + +#endif diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/error.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/error.hpp new file mode 100644 index 0000000..3b59d03 --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/error.hpp @@ -0,0 +1,88 @@ +// +// gnutls/error.hpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_ERROR_HPP +#define BOOST_ASIO_GNUTLS_ERROR_HPP + +#include +#include + +#include + +#include + +namespace boost { +namespace asio { +namespace gnutls { + +class error_category : public boost::system::error_category +{ +public: + char const* name() const BOOST_ASIO_ERROR_CATEGORY_NOEXCEPT { return "GnuTLS"; } + + std::string message(int value) const + { + char const* s = gnutls_strerror(value); + return s ? s : "GnuTLS error"; + } +}; + +namespace error { + +enum stream_errors +{ + stream_truncated = 1, + unspecified_system_error = 2, + unexpected_result = 3 +}; + +static const boost::system::error_category& get_ssl_category() +{ + static error_category instance; + return instance; +} + +static const boost::system::error_category& get_stream_category() { return get_ssl_category(); } + +static const auto& ssl_category BOOST_ASIO_UNUSED_VARIABLE = get_ssl_category(); +static const auto& stream_category BOOST_ASIO_UNUSED_VARIABLE = get_stream_category(); + +} // namespace error +} // namespace gnutls +} // namespace asio +} // namespace boost + +namespace boost { +namespace system { + +template<> struct is_error_code_enum +{ + static const bool value = true; +}; + +} // namespace system +} // namespace boost + +namespace boost { +namespace asio { +namespace gnutls { +namespace error { +inline boost::system::error_code make_error_code(stream_errors e) +{ + return boost::system::error_code( + static_cast(e), get_stream_category()); +} + +} // namespace error +} // namespace gnutls +} // namespace asio +} // namespace boost + +#endif diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/host_name_verification.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/host_name_verification.hpp new file mode 100644 index 0000000..243960d --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/host_name_verification.hpp @@ -0,0 +1,53 @@ +// +// gnutls/host_name_verification.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_HOST_NAME_VERIFICATION_HPP +#define BOOST_ASIO_GNUTLS_HOST_NAME_VERIFICATION_HPP + +#include + +#include + +#include + +namespace boost { +namespace asio { +namespace gnutls { + +// Verifies a certificate against a host_name according to the rules described in RFC 6125. +class host_name_verification +{ +public: + typedef bool result_type; + + explicit host_name_verification(std::string host) + : m_host(std::move(host)) + {} + + // Perform certificate verification. + bool operator()(bool preverified, verify_context& ctx) const + { + // Don't bother looking at certificates that have failed pre-verification. + if (!preverified) return false; + + // Return non-zero for a successful match + return gnutls_x509_crt_check_hostname(ctx.native_handle(), m_host.c_str()) != 0; + } + +private: + // The host name to be checked. + std::string m_host; +}; + +} // namespace gnutls +} // namespace asio +} // namespace boost + +#endif // BOOST_ASIO_GNUTLS_HOST_NAME_VERIFICATION_HPP diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/rfc2818_verification.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/rfc2818_verification.hpp new file mode 100644 index 0000000..ca5c260 --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/rfc2818_verification.hpp @@ -0,0 +1,28 @@ +// +// gnutls/rfc2818_verification.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_RFC2818_VERIFICATION_HPP +#define BOOST_ASIO_GNUTLS_RFC2818_VERIFICATION_HPP + +#include + +namespace boost { +namespace asio { +namespace gnutls { + +// Verifies a certificate against a hostname according to the rules described in RFC 2818. +// Deprecated, use host_name_verification instead. +using rfc2818_verification = host_name_verification; + +} // namespace gnutls +} // namespace asio +} // namespace boost + +#endif // BOOST_ASIO_GNUTLS_RFC2818_VERIFICATION_HPP diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/stream.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/stream.hpp new file mode 100644 index 0000000..a44a21c --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/stream.hpp @@ -0,0 +1,942 @@ +// +// gnutls/stream.hpp +// ~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_STREAM_HPP +#define BOOST_ASIO_GNUTLS_STREAM_HPP + +#include "context.hpp" +#include "stream_base.hpp" + +#include + +#ifndef BOOST_NO_EXCEPTIONS +#include +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace asio { +namespace gnutls { + +template class stream : public stream_base +{ +public: + using next_layer_type = NextLayer; + using lowest_layer_type = + typename std::remove_reference::type::lowest_layer_type; + using executor_type = typename std::remove_reference::type::executor_type; + using io_context = boost::asio::io_context; + + template + stream(Arg&& arg, context& ctx) + : stream_base(ctx) + , m_next_layer(std::forward(arg)) + , m_tls_version(ctx.m_impl->tls_version()) + { + ensure_impl(ctx.m_impl->is_server() ? server : client); + } + + stream(stream&& other) + : stream_base(std::move(other)) + , m_next_layer(std::move(other.m_next_layer)) + , m_verify(std::move(other.m_verify)) + , m_verify_callback(std::move(other.m_verify_callback)) + , m_tls_version(other.m_tls_version) + , m_impl(std::move(other.m_impl)) + { + m_impl->parent = this; + } + + stream(stream const& other) = delete; + ~stream() + { + if (m_impl) + { + m_impl->abort(); + m_impl->parent = nullptr; + } + } + + executor_type get_executor() { return m_next_layer.get_executor(); } + io_context& get_io_context() { return m_next_layer.lowest_layer().get_io_context(); } + const lowest_layer_type& lowest_layer() const { return m_next_layer.lowest_layer(); } + lowest_layer_type& lowest_layer() { return m_next_layer.lowest_layer(); } + const next_layer_type& next_layer() const { return m_next_layer; } + next_layer_type& next_layer() { return m_next_layer; } + + native_handle_type native_handle() { return m_impl->session; } + +#ifndef BOOST_NO_EXCEPTIONS + void set_verify_mode(verify_mode v) + { + error_code ec; + set_verify_mode(v, ec); + } +#endif + + // Warning: for clients only (verify_none or verify_peer) + error_code set_verify_mode(verify_mode v, error_code& ec) + { + m_verify = v; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void set_verify_depth(int depth) + { + error_code ec; + set_verify_depth(depth, ec); + } +#endif + + // Warning: ignored + error_code set_verify_depth(int, error_code& ec) { return ec; } + +#ifndef BOOST_NO_EXCEPTIONS + template void set_verify_callback(VerifyCallback callback) + { + error_code ec; + set_verify_callback(callback, ec); + } +#endif + + template + error_code set_verify_callback(VerifyCallback callback, error_code& ec) + { + m_verify_callback = callback; + return ec; + } + + template + BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, void(error_code)) + async_handshake(handshake_type type, HandshakeHandler&& handler) + { + // If you get an error on the following line it means that your handler does + // not meet the documented type requirements for a HandshakeHandler. + BOOST_ASIO_HANDSHAKE_HANDLER_CHECK(HandshakeHandler, handler) type_check; + + async_callable callable(std::move(handler)); + + if (m_impl->handshake_handler || m_impl->is_handshake_done) + { + post(get_executor(), std::bind(callable, boost::asio::error::operation_not_supported)); + return; + } + + error_code ec; + m_next_layer.non_blocking(true, ec); + if (ec) + { + post(get_executor(), std::bind(callable, ec)); + return; + } + + ensure_impl(type); + m_impl->handshake_handler = std::bind(callable, std::placeholders::_1); + m_impl->handle_handshake(); + return callable.get_completion_result(); + } + + template + BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler, void(error_code, std::size_t)) + async_handshake(handshake_type type, + const ConstBufferSequence& buffers, + BufferedHandshakeHandler&& handler) + { + // If you get an error on the following line it means that your handler does + // not meet the documented type requirements for a BufferedHandshakeHandler. + BOOST_ASIO_BUFFERED_HANDSHAKE_HANDLER_CHECK(BufferedHandshakeHandler, handler) type_check; + + async_callable callable( + std::move(handler)); + + async_handshake(type, std::bind(callable, std::placeholders::_1, std::size_t(0))); + return callable.get_completion_result(); + } + + template + BOOST_ASIO_INITFN_RESULT_TYPE(ShutdownHandler, void(error_code)) + async_shutdown(ShutdownHandler&& handler) + { + // If you get an error on the following line it means that your handler does + // not meet the documented type requirements for a ShutdownHandler. + BOOST_ASIO_SHUTDOWN_HANDLER_CHECK(ShutdownHandler, handler) type_check; + + async_callable callable(std::move(handler)); + + if (m_impl->shutdown_handler || !m_impl->is_handshake_done) + { + post(get_executor(), std::bind(callable, boost::asio::error::operation_not_supported)); + return; + } + + error_code ec; + m_next_layer.non_blocking(true, ec); + if (ec) + { + post(get_executor(), std::bind(callable, ec)); + return; + } + + m_impl->abort(); + m_impl->shutdown_handler = std::bind(callable, std::placeholders::_1); + m_impl->handle_shutdown(); + return callable.get_completion_result(); + } + + template + BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, void(error_code, std::size_t)) + async_read_some(const MutableBufferSequence& buffers, ReadHandler&& handler) + { + // If you get an error on the following line it means that your handler does + // not meet the documented type requirements for a ReadHandler. + BOOST_ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; + + async_callable callable(std::move(handler)); + + if (m_impl->read_handler) + { + post(get_executor(), + std::bind(callable, boost::asio::error::operation_not_supported, std::size_t(0))); + return; + } + + error_code ec; + m_next_layer.non_blocking(true, ec); + if (ec) + { + post(get_executor(), std::bind(callable, ec, std::size_t(0))); + return; + } + + std::size_t bytes_added = 0; + for (auto b = buffer_sequence_begin(buffers), end(buffer_sequence_end(buffers)); b != end; + ++b) + { + auto r = *b; // operator -> might be deleted + if (r.size() == 0) continue; + m_impl->read_buffers.push_back(r); + bytes_added += r.size(); + } + + if (bytes_added == 0) + { + // if we're reading 0 bytes, post handler immediately + post(get_executor(), std::bind(callable, error_code(), std::size_t(0))); + return; + } + + m_impl->read_handler = std::bind(callable, std::placeholders::_1, std::placeholders::_2); + m_impl->bytes_read = 0; + m_impl->async_schedule(); + return callable.get_completion_result(); + } + + template + BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, void(error_code, std::size_t)) + async_write_some(const ConstBufferSequence& buffers, WriteHandler&& handler) + { + // If you get an error on the following line it means that your handler does + // not meet the documented type requirements for a WriteHandler. + BOOST_ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; + + async_callable callable(std::move(handler)); + + if (m_impl->write_handler) + { + post(get_executor(), + std::bind(callable, boost::asio::error::operation_not_supported, std::size_t(0))); + return; + } + + error_code ec; + m_next_layer.non_blocking(true, ec); + if (ec) + { + post(get_executor(), std::bind(callable, ec, std::size_t(0))); + return; + } + + size_t bytes_added = 0; + for (auto b = buffer_sequence_begin(buffers), end(buffer_sequence_end(buffers)); b != end; + ++b) + { + auto r = *b; // operator -> might be deleted + if (r.size() == 0) continue; + m_impl->write_buffers.push_back(r); + bytes_added += r.size(); + } + + if (bytes_added == 0) + { + // if we're writing 0 bytes, post handler immediately + post(get_executor(), std::bind(callable, error_code(), std::size_t(0))); + return; + } + + m_impl->write_handler = std::bind(callable, std::placeholders::_1, std::placeholders::_2); + m_impl->bytes_written = 0; + m_impl->async_schedule(); + return callable.get_completion_result(); + } + + void handshake(handshake_type type) + { + error_code ec; + handshake(type, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } + + error_code handshake(handshake_type type, error_code& ec) + { + if (m_impl->is_handshake_done) return ec = boost::asio::error::operation_not_supported; + + ensure_impl(type); + int ret; + do { + ret = gnutls_handshake(m_impl->session); + } while (ret != GNUTLS_E_SUCCESS && !gnutls_error_is_fatal(ret)); + + if (ret == GNUTLS_E_PREMATURE_TERMINATION) + return ec = error::stream_truncated; + else if (ret != GNUTLS_E_SUCCESS) + return ec = error_code(ret, error::get_ssl_category()); + + m_impl->is_handshake_done = true; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void handshake(handshake_type type, const ConstBufferSequence& buffers) + { + error_code ec; + handshake(type, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + template + error_code handshake(handshake_type type, const ConstBufferSequence& buffers, error_code& ec) + { + handshake(type, ec); + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void shutdown() + { + error_code ec; + shutdown(ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code shutdown(error_code& ec) + { + int ret; + do { + ret = gnutls_bye(m_impl->session, GNUTLS_SHUT_RDWR); + } while (ret != GNUTLS_E_SUCCESS && !gnutls_error_is_fatal(ret)); + + if (ret == GNUTLS_E_PREMATURE_TERMINATION) + return ec = error::stream_truncated; + else if (ret != GNUTLS_E_SUCCESS) + return ec = error_code(ret, error::get_ssl_category()); + + m_impl->is_handshake_done = false; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + template size_t read_some(const MutableBufferSequence& buffers) + { + error_code ec; + std::size_t bytes_read = read_some(buffers, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + return bytes_read; + } +#endif + + template + size_t read_some(const MutableBufferSequence& buffers, error_code& ec) + { + if (m_impl->read_handler || !m_impl->is_handshake_done) + { + ec = boost::asio::error::operation_not_supported; + return 0; + } + + std::copy(buffer_sequence_begin(buffers), + buffer_sequence_end(buffers), + std::back_inserter(m_impl->read_buffers)); + + std::size_t bytes_read = m_impl->recv_some(ec); + m_impl->read_buffers.clear(); + return bytes_read; + } + +#ifndef BOOST_NO_EXCEPTIONS + template + std::size_t write_some(const ConstBufferSequence& buffers) + { + error_code ec; + std::size_t bytes_written = write_some(buffers, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + return bytes_written; + } +#endif + + template + std::size_t write_some(const ConstBufferSequence& buffers, error_code& ec) + { + if (m_impl->write_handler || !m_impl->is_handshake_done) + { + ec = boost::asio::error::operation_not_supported; + return 0; + } + + std::copy(buffer_sequence_begin(buffers), + buffer_sequence_end(buffers), + std::back_inserter(m_impl->write_buffers)); + + std::size_t bytes_written = m_impl->send_some(ec); + m_impl->write_buffers.clear(); + return bytes_written; + } + + // ---------- SNI extension ---------- + +#ifndef BOOST_NO_EXCEPTIONS + void set_host_name(std::string const& name) + { + error_code ec; + set_host_name(name, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code set_host_name(std::string const& name, error_code& ec) + { + int ret = + gnutls_server_name_set(m_impl->session, GNUTLS_NAME_DNS, name.c_str(), name.size()); + return ec = ret == GNUTLS_E_SUCCESS ? error_code() + : error_code(ret, error::get_ssl_category()); + } + + // ----------------------------------- + +private: + template class async_callable + { + public: + async_callable(Handler&& h) + : m_impl(std::make_shared(std::move(h))) + {} + + void operator()(Args... args) const + { + m_impl->completion.completion_handler(std::forward(args)...); + } + + auto get_completion_result() { return m_impl->completion.result.get(); } + + private: + struct impl + { + impl(Handler&& h) + : handler(std::move(h)) + , completion(handler) + {} + + Handler handler; + boost::asio::async_completion completion; + }; + + std::shared_ptr m_impl; + }; + + enum class direction + { + none, + read, + write + }; + + next_layer_type m_next_layer; + verify_mode m_verify = -1; + std::function m_verify_callback; + unsigned int m_tls_version; // X*10 + Y => TLS X.Y, 0*10 + Z => SSL Z + + struct impl : public std::enable_shared_from_this + { + impl(stream* p, handshake_type t) + : type(t) + , parent(p) + { + int ret = gnutls_init( + &session, (type == client ? GNUTLS_CLIENT : GNUTLS_SERVER) | GNUTLS_NONBLOCK); + if (ret != GNUTLS_E_SUCCESS) + throw std::runtime_error("gnutls_init failed: " + + std::string(gnutls_strerror(ret))); + + gnutls_session_set_ptr(session, this); + gnutls_handshake_set_post_client_hello_function(session, post_client_hello_func); + + gnutls_transport_set_ptr(session, this); + gnutls_transport_set_push_function(session, push_func); + gnutls_transport_set_pull_function(session, pull_func); + + auto context_impl = parent->m_context_impl; + auto const opts = context_impl->opts; + auto const tls_version = parent->m_tls_version; + + std::ostringstream priority; + priority << "NORMAL"; + if (opts & context::default_workarounds) priority << ":%COMPAT"; + if (tls_version > 0 && tls_version < 10 && !(opts & context::no_sslv3)) + priority << ":+VERS-SSL3.0"; + if (tls_version >= 10) + priority << ":-VERS-TLS-ALL:+VERS-TLS" << (tls_version / 10) << '.' + << (tls_version % 10); + + char const* err_pos = nullptr; + ret = gnutls_priority_set_direct(session, priority.str().c_str(), &err_pos); + if (ret != GNUTLS_E_SUCCESS) + throw std::runtime_error("gnutls_priority_set_direct failed for \"" + + priority.str() + + "\": " + std::string(gnutls_strerror(ret))); + + gnutls_certificate_set_verify_function(context_impl->cred, verify_func); + ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, context_impl->cred); + if (ret != GNUTLS_E_SUCCESS) + throw std::runtime_error("gnutls_credentials_set failed: " + + std::string(gnutls_strerror(ret))); + } + + ~impl() { gnutls_deinit(session); } + + template void post(Function&& function) const + { + if (parent) boost::asio::post(parent->get_executor(), std::forward(function)); + } + + void abort() + { + if (auto handler = std::exchange(handshake_handler, nullptr)) + handler(boost::asio::error::operation_aborted); + if (auto handler = std::exchange(shutdown_handler, nullptr)) + handler(boost::asio::error::operation_aborted); + if (auto handler = std::exchange(read_handler, nullptr)) + handler(boost::asio::error::operation_aborted, std::size_t(0)); + if (auto handler = std::exchange(write_handler, nullptr)) + handler(boost::asio::error::operation_aborted, std::size_t(0)); + } + + std::string get_server_name() const + { + char buf[256]; + size_t len = sizeof(buf); + unsigned int type = GNUTLS_NAME_DNS; + int ret = gnutls_server_name_get(session, buf, &len, &type, 0); + return ret == GNUTLS_E_SUCCESS ? std::string(buf, len) : ""; + } + + bool want_read() const { return want_direction == direction::read || read_handler; } + bool want_write() const { return want_direction == direction::write || write_handler; } + + void async_schedule() + { + constexpr auto wait_read = std::remove_reference::type::wait_read; + constexpr auto wait_write = std::remove_reference::type::wait_write; + + if (!parent) return; + auto& next_layer = parent->m_next_layer; + + // Start a read operation if GnuTLS wants one + if (want_read() && !std::exchange(is_reading, true)) + { + if (gnutls_record_check_pending(session) > 0 && read_handler) + handle_read(); + else + next_layer.async_wait(wait_read, + std::bind(&impl::handle_read, + this->shared_from_this(), + std::placeholders::_1)); + } + + // Start a write operation if GnuTLS wants one + if (want_write() && !std::exchange(is_writing, true)) + { + next_layer.async_wait(wait_write, + std::bind(&impl::handle_write, + this->shared_from_this(), + std::placeholders::_1)); + } + } + + void handle_read(error_code ec = {}) + { + namespace error = boost::asio::error; + + is_reading = false; + if (read_handler) + { + if (!ec) bytes_read += recv_some(ec); + + if (ec == error::try_again || ec == error::would_block) return async_schedule(); + + read_buffers.clear(); + auto handler = std::exchange(read_handler, nullptr); + post(std::bind(std::move(handler), ec, std::exchange(bytes_read, std::size_t(0)))); + return; + } + + if (handshake_handler) return handle_handshake(ec); + if (shutdown_handler) return handle_shutdown(ec); + } + + void handle_write(error_code ec = {}) + { + namespace error = boost::asio::error; + + is_writing = false; + if (write_handler) + { + if (!ec) bytes_written += send_some(ec); + + if (ec == error::try_again || ec == error::would_block) return async_schedule(); + + write_buffers.clear(); + auto handler = std::exchange(write_handler, nullptr); + post(std::bind( + std::move(handler), ec, std::exchange(bytes_written, std::size_t(0)))); + } + + if (handshake_handler) return handle_handshake(ec); + if (shutdown_handler) return handle_shutdown(ec); + } + + void handle_handshake(error_code ec = {}) + { + if (!ec) + { + int ret = gnutls_handshake(session); + if (ret == GNUTLS_E_AGAIN) + { + want_direction = gnutls_record_get_direction(session) == 0 ? direction::read + : direction::write; + return async_schedule(); + } + + want_direction = direction::none; + if (ret == GNUTLS_E_SUCCESS) + is_handshake_done = true; + else if (ret == GNUTLS_E_PREMATURE_TERMINATION) + ec = error::stream_truncated; + else + ec = error_code(ret, error::get_ssl_category()); + } + + if (handshake_handler != nullptr) + { + auto handler = std::exchange(handshake_handler, nullptr); + post(std::bind(std::move(handler), ec)); + } + } + + bool is_safe_renegotiation_enabled() + { + return gnutls_safe_renegotiation_status(session) != 0; + } + + void handle_shutdown(error_code ec = {}) + { + if (!ec) + { + int ret = gnutls_bye(session, GNUTLS_SHUT_RDWR); + if (ret == GNUTLS_E_AGAIN) + { + want_direction = gnutls_record_get_direction(session) == 0 ? direction::read + : direction::write; + return async_schedule(); + } + + want_direction = direction::none; + if (ret == GNUTLS_E_SUCCESS) + is_handshake_done = false; + else + ec = error_code(ret, error::get_ssl_category()); + } + + auto handler = std::exchange(shutdown_handler, nullptr); + post(std::bind(std::move(handler), ec)); + } + + std::size_t recv_some(error_code& ec) + { + std::size_t bytes_read = 0; + while (!read_buffers.empty()) + { + auto& front = read_buffers.front(); + int ret = gnutls_record_recv(session, front.data(), front.size()); + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN) + ec = boost::asio::error::would_block; + else if (ret == GNUTLS_E_PREMATURE_TERMINATION) + ec = error::stream_truncated; + else if (ret == GNUTLS_E_REHANDSHAKE && is_safe_renegotiation_enabled()) + handle_handshake(ec); + else if (gnutls_error_is_fatal(ret)) + ec = error_code(ret, error::get_ssl_category()); + else + continue; + + break; + } + + if (front.size() > 0 && ret == 0) + { + ec = boost::asio::error::eof; + break; + } + + front += ret; + bytes_read += ret; + if (front.size() == 0) read_buffers.pop_front(); + + if (gnutls_record_check_pending(session) == 0) break; + } + + if (bytes_read > 0) ec.clear(); + + return bytes_read; + } + + std::size_t send_some(error_code& ec) + { + gnutls_record_cork(session); + while (!write_buffers.empty()) + { + auto& front = write_buffers.front(); + int ret = gnutls_record_send(session, front.data(), front.size()); + if (ret < 0) break; + + front += ret; + if (front.size() == 0) write_buffers.pop_front(); + } + + std::size_t bytes_written = 0; + do { + int ret = gnutls_record_uncork(session, 0); + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN) + ec = boost::asio::error::would_block; + else if (ret == GNUTLS_E_PREMATURE_TERMINATION) + ec = error::stream_truncated; + else if (gnutls_error_is_fatal(ret)) + ec = error_code(ret, error::get_ssl_category()); + else + continue; + + break; + } + + bytes_written += ret; + } while (gnutls_record_check_corked(session) > 0); + + if (bytes_written > 0) ec.clear(); + + return bytes_written; + } + + static ssize_t pull_func(void* ptr, void* buffer, std::size_t size) + { + namespace error = boost::asio::error; + + auto* im = static_cast(ptr); + if (!im->parent) + { + gnutls_transport_set_errno(im->session, ECONNRESET); + return -1; + } + + auto& next_layer = im->parent->m_next_layer; + error_code ec; + std::size_t bytes_read = next_layer.read_some(boost::asio::buffer(buffer, size), ec); + if (ec && ec != error::eof && ec != error::connection_reset) // consider reset as close + { + gnutls_transport_set_errno( + im->session, + (ec == error::try_again || ec == error::would_block) ? EAGAIN : ECONNRESET); + return -1; + } + + gnutls_transport_set_errno(im->session, 0); + return ssize_t(bytes_read); + } + + static ssize_t push_func(void* ptr, const void* data, std::size_t len) + { + namespace error = boost::asio::error; + + auto* im = static_cast(ptr); + if (!im->parent) + { + gnutls_transport_set_errno(im->session, ECONNRESET); + return -1; + } + + auto& next_layer = im->parent->m_next_layer; + error_code ec; + std::size_t bytes_written = + next_layer.write_some(boost::asio::const_buffer(data, len), ec); + if (ec) + { + gnutls_transport_set_errno( + im->session, + (ec == error::try_again || ec == error::would_block) ? EAGAIN : ECONNRESET); + return -1; + } + + gnutls_transport_set_errno(im->session, 0); + return ssize_t(bytes_written); + } + + static int verify_func(gnutls_session_t session) + { + auto* im = static_cast(gnutls_session_get_ptr(session)); + if (!im->parent) return GNUTLS_E_INVALID_SESSION; + auto context_impl = im->parent->m_context_impl; + + auto verify = im->parent->m_verify >= 0 ? im->parent->m_verify : context_impl->verify; + auto verify_callback = im->parent->m_verify_callback ? im->parent->m_verify_callback + : context_impl->verify_callback; + + if (!(verify & context::verify_peer)) + return GNUTLS_E_SUCCESS; // no verification requested + + if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) + return GNUTLS_E_NO_CERTIFICATE_FOUND; + + unsigned int count = 0; + gnutls_datum_t const* array = gnutls_certificate_get_peers(session, &count); + if (!array || count == 0) return GNUTLS_E_NO_CERTIFICATE_FOUND; + + gnutls_x509_crt_t cert; + gnutls_x509_crt_init(&cert); + int ret = gnutls_x509_crt_import(cert, &array[0], GNUTLS_X509_FMT_DER); + if (ret != GNUTLS_E_SUCCESS) + { + gnutls_x509_crt_deinit(cert); + return ret; + } + + bool verified = false; + unsigned int status = 0; + ret = gnutls_certificate_verify_peers2(session, &status); + if (ret == GNUTLS_E_SUCCESS && !(status & GNUTLS_CERT_INVALID)) verified = true; + + if (verify_callback) + { + verify_context ctx(cert); + verified = verify_callback(verified, ctx); + } + + gnutls_x509_crt_deinit(cert); + return verified ? GNUTLS_E_SUCCESS : GNUTLS_E_CERTIFICATE_ERROR; + } + + static int post_client_hello_func(gnutls_session_t session) + { + auto* im = static_cast(gnutls_session_get_ptr(session)); + if (!im->parent) return GNUTLS_E_INVALID_SESSION; + auto context_impl = im->parent->m_context_impl; + + auto& callback = context_impl->server_name_callback; + if (!callback) return GNUTLS_E_SUCCESS; + + if (!callback(*im->parent, im->get_server_name())) return GNUTLS_E_UNRECOGNIZED_NAME; + + // context may have been switched + context_impl = im->parent->m_context_impl; + + // set credentials now + gnutls_certificate_set_verify_function(context_impl->cred, verify_func); + int ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, context_impl->cred); + if (ret != GNUTLS_E_SUCCESS) return ret; + + // set certificate request + auto const verify = context_impl->verify; + gnutls_certificate_request_t req = GNUTLS_CERT_IGNORE; + if (verify & context::verify_peer) + { + if (verify & context::verify_fail_if_no_peer_cert) + req = GNUTLS_CERT_REQUIRE; + else + req = GNUTLS_CERT_REQUEST; + } + gnutls_certificate_server_set_request(session, req); + + return GNUTLS_E_SUCCESS; + } + + const handshake_type type; + stream* parent; + + gnutls_session_t session; + + direction want_direction = direction::none; + bool is_handshake_done = false; + bool is_reading = false; + bool is_writing = false; + + std::function handshake_handler; + std::function shutdown_handler; + std::function read_handler; + std::function write_handler; + + std::list read_buffers; + std::list write_buffers; + + std::size_t bytes_read = 0; + std::size_t bytes_written = 0; + }; + + std::shared_ptr ensure_impl(handshake_type type) + { + if (!m_impl || m_impl->type != type) + if (auto old = std::exchange(m_impl, std::make_shared(this, type))) + { + old->abort(); + old->parent = nullptr; + } + return m_impl; + } + + std::shared_ptr m_impl; +}; + +} // namespace gnutls +} // namespace asio +} // namespace boost + +#endif diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/stream_base.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/stream_base.hpp new file mode 100644 index 0000000..c1bda18 --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/stream_base.hpp @@ -0,0 +1,71 @@ +// +// gnutls/stream_base.hpp +// ~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_STREAM_BASE_HPP +#define BOOST_ASIO_GNUTLS_STREAM_BASE_HPP + +#include "context.hpp" + +#include + +#include + +#include +#include +#include + +namespace boost { +namespace asio { +namespace gnutls { + +class stream_base +{ +public: + using error_code = boost::system::error_code; + using native_handle_type = gnutls_session_t; + + enum handshake_type + { + client, + server + }; + + stream_base(context& ctx) { set_context(ctx); } + stream_base(stream_base&& other) + : m_context_impl(std::move(other.m_context_impl)) + {} + stream_base(stream_base const& other) = delete; + virtual ~stream_base() = default; + + context& get_context() const + { + if (!m_context_impl->parent) throw std::logic_error("access to destroyed ssl context"); + + return *m_context_impl->parent; + } + + void set_context(context& ctx) { m_context_impl = ctx.m_impl; } + + virtual native_handle_type native_handle() = 0; + +#ifndef BOOST_NO_EXCEPTIONS + virtual void set_host_name(std::string const& name) = 0; +#endif + virtual error_code set_host_name(std::string const& name, error_code& ec) = 0; + +protected: + std::shared_ptr m_context_impl; +}; + +} // namespace gnutls +} // namespace asio +} // namespace boost + +#endif diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/verify_context.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/verify_context.hpp new file mode 100644 index 0000000..22cfca1 --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/verify_context.hpp @@ -0,0 +1,40 @@ +// +// gnutls/context.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_VERIFY_CONTEXT_HPP +#define BOOST_ASIO_GNUTLS_VERIFY_CONTEXT_HPP + +#include + +namespace boost { +namespace asio { +namespace gnutls { + +class verify_context +{ +public: + using native_handle_type = gnutls_x509_crt_t; + + explicit verify_context(native_handle_type cert) + : m_cert(cert) + {} + + native_handle_type native_handle() { return m_cert; } + +private: + gnutls_x509_crt_t m_cert; +}; + +} // namespace gnutls +} // namespace asio +} // namespace boost + +#endif + diff --git a/deps/asio-gnutls/test/gnutls/Jamfile.v2 b/deps/asio-gnutls/test/gnutls/Jamfile.v2 new file mode 100644 index 0000000..7dfc11d --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/Jamfile.v2 @@ -0,0 +1,55 @@ +# +# Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +# +# 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_0.txt) +# + +import os ; +import feature ; + +lib gnutls ; +lib crypto ; + +lib socket ; # SOLARIS +lib nsl ; # SOLARIS +lib ws2_32 ; # NT +lib mswsock ; # NT +lib ipv6 ; # HPUX +lib network ; # HAIKU + +local USE_SELECT = + BOOST_ASIO_DISABLE_EPOLL + BOOST_ASIO_DISABLE_KQUEUE + BOOST_ASIO_DISABLE_IOCP + ; + +project + : requirements + ../../include + /boost/system//boost_system + BOOST_ALL_NO_LIB=1 + multi + solaris:socket + solaris:nsl + windows:_WIN32_WINNT=0x0501 + windows,gcc:ws2_32 + windows,gcc:mswsock + windows,gcc-cygwin:__USE_W32_SOCKETS + hpux,gcc:_XOPEN_SOURCE_EXTENDED + hpux:ipv6 + haiku:network + ; + +test-suite "asio-gnutls" : + [ compile context_base.cpp ] + [ compile context_base.cpp : $(USE_SELECT) : context_base_select ] + [ compile context.cpp ] + [ compile context.cpp : $(USE_SELECT) : context_select ] + [ compile error.cpp ] + [ compile error.cpp : $(USE_SELECT) : error_select ] + [ compile stream_base.cpp ] + [ compile stream_base.cpp : $(USE_SELECT) : stream_base_select ] + [ compile stream.cpp ] + [ compile stream.cpp : $(USE_SELECT) : stream_select ] + ; diff --git a/deps/asio-gnutls/test/gnutls/context.cpp b/deps/asio-gnutls/test/gnutls/context.cpp new file mode 100644 index 0000000..c832dc8 --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/context.cpp @@ -0,0 +1,65 @@ +// +// context.cpp +// ~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include "../unit_test.hpp" + +//------------------------------------------------------------------------------ + +// gnutls_context_compile test +// ~~~~~~~~~~~~~~~~~~~~~~~ +// The following test checks that all public member functions on the class +// gnutls::context compile and link correctly. Runtime failures are ignored. + +namespace gnutls_context_compile { + +bool verify_callback(bool, boost::asio::gnutls::verify_context&) { return false; } + +bool server_name_callback(boost::asio::gnutls::stream_base& s, std::string name) { return false; } + +void test() +{ + using namespace boost::asio; + + try + { + boost::asio::gnutls::context context(boost::asio::gnutls::context::tls); + boost::system::error_code ec; + + context.set_verify_mode(gnutls::context::verify_none); + context.set_verify_mode(gnutls::context::verify_none, ec); + + context.set_verify_depth(1); + context.set_verify_depth(1, ec); + + context.set_verify_callback(verify_callback); + context.set_verify_callback(verify_callback, ec); + + // SNI extension + + context.set_server_name_callback(server_name_callback); + context.set_server_name_callback(server_name_callback, ec); + } + catch (std::exception&) + {} +} + +} // namespace gnutls_context_compile + +//------------------------------------------------------------------------------ + +BOOST_ASIO_TEST_SUITE("gnutls/context", BOOST_ASIO_TEST_CASE(gnutls_context_compile::test)) diff --git a/deps/asio-gnutls/test/gnutls/context_base.cpp b/deps/asio-gnutls/test/gnutls/context_base.cpp new file mode 100644 index 0000000..5a474be --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/context_base.cpp @@ -0,0 +1,21 @@ +// +// context_base.cpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include "../unit_test.hpp" + +BOOST_ASIO_TEST_SUITE("gnutls/context_base", BOOST_ASIO_TEST_CASE(null_test)) diff --git a/deps/asio-gnutls/test/gnutls/error.cpp b/deps/asio-gnutls/test/gnutls/error.cpp new file mode 100644 index 0000000..af47a5c --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/error.cpp @@ -0,0 +1,21 @@ +// +// error.cpp +// ~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include "../unit_test.hpp" + +BOOST_ASIO_TEST_SUITE("gnutls/error", BOOST_ASIO_TEST_CASE(null_test)) diff --git a/deps/asio-gnutls/test/gnutls/host_name_verification.cpp b/deps/asio-gnutls/test/gnutls/host_name_verification.cpp new file mode 100644 index 0000000..a161061 --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/host_name_verification.cpp @@ -0,0 +1,21 @@ +// +// host_name_verification.cpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include "../unit_test.hpp" + +BOOST_ASIO_TEST_SUITE("gnutls/host_name_verification", BOOST_ASIO_TEST_CASE(null_test)) diff --git a/deps/asio-gnutls/test/gnutls/rfc2818_verification.cpp b/deps/asio-gnutls/test/gnutls/rfc2818_verification.cpp new file mode 100644 index 0000000..9a524f6 --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/rfc2818_verification.cpp @@ -0,0 +1,21 @@ +// +// rfc2818_verification.cpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include "../unit_test.hpp" + +BOOST_ASIO_TEST_SUITE("gnutls/rfc2818_verification", BOOST_ASIO_TEST_CASE(null_test)) diff --git a/deps/asio-gnutls/test/gnutls/stream.cpp b/deps/asio-gnutls/test/gnutls/stream.cpp new file mode 100644 index 0000000..cbe13cb --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/stream.cpp @@ -0,0 +1,149 @@ +// +// stream.cpp +// ~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include "../unit_test.hpp" +#include +#include + +//------------------------------------------------------------------------------ + +// gnutls_stream_compile test +// ~~~~~~~~~~~~~~~~~~~~~~~ +// The following test checks that all public member functions on the class +// gnutls::stream::socket compile and link correctly. Runtime failures are ignored. + +namespace gnutls_stream_compile { + +bool verify_callback(bool, boost::asio::gnutls::verify_context&) { return false; } + +void handshake_handler(const boost::system::error_code&) {} + +void buffered_handshake_handler(const boost::system::error_code&, std::size_t) {} + +void shutdown_handler(const boost::system::error_code&) {} + +void write_some_handler(const boost::system::error_code&, std::size_t) {} + +void read_some_handler(const boost::system::error_code&, std::size_t) {} + +void test() +{ + using namespace boost::asio; + namespace ip = boost::asio::ip; + + try + { + io_context ioc; + char mutable_char_buffer[128] = ""; + const char const_char_buffer[128] = ""; + std::string hostname = "hostname"; + boost::asio::gnutls::context context(boost::asio::gnutls::context::tls); + boost::system::error_code ec; + + // gnutls::stream constructors. + + gnutls::stream stream1(ioc, context); + ip::tcp::socket socket1(ioc, ip::tcp::v4()); + gnutls::stream stream2(socket1, context); + + // basic_io_object functions. + + gnutls::stream::executor_type ex = stream1.get_executor(); + (void)ex; + + // gnutls::stream functions. + + stream1.set_verify_mode(gnutls::verify_none); + stream1.set_verify_mode(gnutls::verify_none, ec); + + stream1.set_verify_depth(1); + stream1.set_verify_depth(1, ec); + + stream1.set_verify_callback(verify_callback); + stream1.set_verify_callback(verify_callback, ec); + + gnutls_session_t session1 = stream1.native_handle(); + (void)session1; + + gnutls::stream::lowest_layer_type& lowest_layer = stream1.lowest_layer(); + (void)lowest_layer; + + const gnutls::stream& stream3 = stream1; + const gnutls::stream::lowest_layer_type& lowest_layer2 = + stream3.lowest_layer(); + (void)lowest_layer2; + + stream1.handshake(gnutls::stream_base::client); + stream1.handshake(gnutls::stream_base::server); + stream1.handshake(gnutls::stream_base::client, ec); + stream1.handshake(gnutls::stream_base::server, ec); + + stream1.handshake(gnutls::stream_base::client, buffer(mutable_char_buffer)); + stream1.handshake(gnutls::stream_base::server, buffer(mutable_char_buffer)); + stream1.handshake(gnutls::stream_base::client, buffer(const_char_buffer)); + stream1.handshake(gnutls::stream_base::server, buffer(const_char_buffer)); + stream1.handshake(gnutls::stream_base::client, buffer(mutable_char_buffer), ec); + stream1.handshake(gnutls::stream_base::server, buffer(mutable_char_buffer), ec); + stream1.handshake(gnutls::stream_base::client, buffer(const_char_buffer), ec); + stream1.handshake(gnutls::stream_base::server, buffer(const_char_buffer), ec); + + stream1.async_handshake(gnutls::stream_base::client, handshake_handler); + stream1.async_handshake(gnutls::stream_base::server, handshake_handler); + + stream1.async_handshake( + gnutls::stream_base::client, buffer(mutable_char_buffer), buffered_handshake_handler); + stream1.async_handshake( + gnutls::stream_base::server, buffer(mutable_char_buffer), buffered_handshake_handler); + stream1.async_handshake( + gnutls::stream_base::client, buffer(const_char_buffer), buffered_handshake_handler); + stream1.async_handshake( + gnutls::stream_base::server, buffer(const_char_buffer), buffered_handshake_handler); + + stream1.shutdown(); + stream1.shutdown(ec); + + stream1.async_shutdown(shutdown_handler); + + stream1.write_some(buffer(mutable_char_buffer)); + stream1.write_some(buffer(const_char_buffer)); + stream1.write_some(buffer(mutable_char_buffer), ec); + stream1.write_some(buffer(const_char_buffer), ec); + + stream1.async_write_some(buffer(mutable_char_buffer), write_some_handler); + stream1.async_write_some(buffer(const_char_buffer), write_some_handler); + + stream1.read_some(buffer(mutable_char_buffer)); + stream1.read_some(buffer(mutable_char_buffer), ec); + + stream1.async_read_some(buffer(mutable_char_buffer), read_some_handler); + + // SNI extension + + stream1.set_host_name(hostname); + stream1.set_host_name(hostname, ec); + } + catch (std::exception&) + { + } +} + +} // namespace gnutls_stream_compile + +//------------------------------------------------------------------------------ + +BOOST_ASIO_TEST_SUITE("gnutls/stream", BOOST_ASIO_TEST_CASE(gnutls_stream_compile::test)) diff --git a/deps/asio-gnutls/test/gnutls/stream_base.cpp b/deps/asio-gnutls/test/gnutls/stream_base.cpp new file mode 100644 index 0000000..13215aa --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/stream_base.cpp @@ -0,0 +1,21 @@ +// +// stream_base.cpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// 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_0.txt) +// + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include "../unit_test.hpp" + +BOOST_ASIO_TEST_SUITE("gnutls/stream_base", BOOST_ASIO_TEST_CASE(null_test)) diff --git a/deps/asio-gnutls/test/unit_test.hpp b/deps/asio-gnutls/test/unit_test.hpp new file mode 100644 index 0000000..260a381 --- /dev/null +++ b/deps/asio-gnutls/test/unit_test.hpp @@ -0,0 +1,177 @@ +// +// unit_test.hpp +// ~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// 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_0.txt) +// + +#ifndef UNIT_TEST_HPP +#define UNIT_TEST_HPP + +#include +#include +#include + +#if defined(__sun) +# include // Needed for lrand48. +#endif // defined(__sun) + +#if defined(__BORLANDC__) + +// Prevent use of intrinsic for strcmp. +# include +# undef strcmp + +// Suppress error about condition always being true. +# pragma option -w-ccc + +#endif // defined(__BORLANDC__) + +#if defined(BOOST_ASIO_MSVC) +# pragma warning (disable:4127) +# pragma warning (push) +# pragma warning (disable:4244) +# pragma warning (disable:4702) +#endif // defined(BOOST_ASIO_MSVC) + +#if !defined(BOOST_ASIO_TEST_IOSTREAM) +# define BOOST_ASIO_TEST_IOSTREAM std::cerr +#endif // !defined(BOOST_ASIO_TEST_IOSTREAM) + +namespace boost { +namespace asio { +namespace detail { + +inline const char*& test_name() +{ + static const char* name = 0; + return name; +} + +inline atomic_count& test_errors() +{ + static atomic_count errors(0); + return errors; +} + +inline void begin_test_suite(const char* name) +{ + boost::asio::detail::test_name(); + boost::asio::detail::test_errors(); + BOOST_ASIO_TEST_IOSTREAM << name << " test suite begins" << std::endl; +} + +inline int end_test_suite(const char* name) +{ + BOOST_ASIO_TEST_IOSTREAM << name << " test suite ends" << std::endl; + BOOST_ASIO_TEST_IOSTREAM << "\n*** "; + long errors = boost::asio::detail::test_errors(); + if (errors == 0) + BOOST_ASIO_TEST_IOSTREAM << "No errors detected."; + else if (errors == 1) + BOOST_ASIO_TEST_IOSTREAM << "1 error detected."; + else + BOOST_ASIO_TEST_IOSTREAM << errors << " errors detected." << std::endl; + BOOST_ASIO_TEST_IOSTREAM << std::endl; + return errors == 0 ? 0 : 1; +} + +template +inline void run_test(const char* name) +{ + test_name() = name; + long errors_before = boost::asio::detail::test_errors(); + Test(); + if (test_errors() == errors_before) + BOOST_ASIO_TEST_IOSTREAM << name << " passed" << std::endl; + else + BOOST_ASIO_TEST_IOSTREAM << name << " failed" << std::endl; +} + +template +inline void compile_test(const char* name) +{ + BOOST_ASIO_TEST_IOSTREAM << name << " passed" << std::endl; +} + +#if defined(BOOST_ASIO_NO_EXCEPTIONS) + +template +void throw_exception(const T& t) +{ + BOOST_ASIO_TEST_IOSTREAM << "Exception: " << t.what() << std::endl; + std::abort(); +} + +#endif // defined(BOOST_ASIO_NO_EXCEPTIONS) + +} // namespace detail +} // namespace asio +} // namespace boost + +#define BOOST_ASIO_CHECK(expr) \ + do { if (!(expr)) { \ + BOOST_ASIO_TEST_IOSTREAM << __FILE__ << "(" << __LINE__ << "): " \ + << boost::asio::detail::test_name() << ": " \ + << "check '" << #expr << "' failed" << std::endl; \ + ++boost::asio::detail::test_errors(); \ + } } while (0) + +#define BOOST_ASIO_CHECK_MESSAGE(expr, msg) \ + do { if (!(expr)) { \ + BOOST_ASIO_TEST_IOSTREAM << __FILE__ << "(" << __LINE__ << "): " \ + << boost::asio::detail::test_name() << ": " \ + << msg << std::endl; \ + ++boost::asio::detail::test_errors(); \ + } } while (0) + +#define BOOST_ASIO_WARN_MESSAGE(expr, msg) \ + do { if (!(expr)) { \ + BOOST_ASIO_TEST_IOSTREAM << __FILE__ << "(" << __LINE__ << "): " \ + << boost::asio::detail::test_name() << ": " \ + << msg << std::endl; \ + } } while (0) + +#define BOOST_ASIO_ERROR(msg) \ + do { \ + BOOST_ASIO_TEST_IOSTREAM << __FILE__ << "(" << __LINE__ << "): " \ + << boost::asio::detail::test_name() << ": " \ + << msg << std::endl; \ + ++boost::asio::detail::test_errors(); \ + } while (0) + +#define BOOST_ASIO_TEST_SUITE(name, tests) \ + int main() \ + { \ + boost::asio::detail::begin_test_suite(name); \ + tests \ + return boost::asio::detail::end_test_suite(name); \ + } + +#define BOOST_ASIO_TEST_CASE(test) \ + boost::asio::detail::run_test<&test>(#test); + +#define BOOST_ASIO_COMPILE_TEST_CASE(test) \ + boost::asio::detail::compile_test<&test>(#test); + +inline void null_test() +{ +} + +#if defined(__GNUC__) && defined(_AIX) + +// AIX needs this symbol defined in asio, even if it doesn't do anything. +int test_main(int, char**) +{ +} + +#endif // defined(__GNUC__) && defined(_AIX) + +#if defined(BOOST_ASIO_MSVC) +# pragma warning (pop) +#endif // defined(BOOST_ASIO_MSVC) + +#endif // UNIT_TEST_HPP diff --git a/deps/try_signal/CMakeLists.txt b/deps/try_signal/CMakeLists.txt new file mode 100644 index 0000000..945cd6c --- /dev/null +++ b/deps/try_signal/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12) +project(try_signal) + +add_library(try_signal signal_error_code try_signal) +target_include_directories(try_signal PUBLIC .) + diff --git a/deps/try_signal/Jamfile b/deps/try_signal/Jamfile new file mode 100644 index 0000000..ec001a8 --- /dev/null +++ b/deps/try_signal/Jamfile @@ -0,0 +1,18 @@ +lib try_signal + : # sources + signal_error_code.cpp try_signal.cpp + : # requirements + : # default build + static + : # usage requirements + . + ; + +exe test : test.cpp : try_signal static ; +explicit test ; + +exe example : example.cpp : try_signal static ; +explicit example ; + +install stage_test : test : . ; + diff --git a/deps/try_signal/LICENSE b/deps/try_signal/LICENSE new file mode 100644 index 0000000..1523026 --- /dev/null +++ b/deps/try_signal/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +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 copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (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/deps/try_signal/README.rst b/deps/try_signal/README.rst new file mode 100644 index 0000000..22cbe18 --- /dev/null +++ b/deps/try_signal/README.rst @@ -0,0 +1,53 @@ +try_signal +========== + +.. image:: https://travis-ci.org/arvidn/try_signal.svg?branch=master + :target: https://travis-ci.org/arvidn/try_signal + +.. image:: https://ci.appveyor.com/api/projects/status/le8jjroaai8081f1?svg=true + :target: https://ci.appveyor.com/project/arvidn/try-signal/branch/master + +The ``try_signal`` library provide a way to turn signals into C++ exceptions. +This is especially useful when performing disk I/O via memory mapped files, +where I/O errors are reported as ``SIGBUS`` and ``SIGSEGV`` or as structured +exceptions on windows. + +The function ``try_signal`` takes a function object that will be executed once. +If the function causes a signal (or structured exception) to be raised, it will +throw a C++ exception. Note that RAII may not be relied upon within this function. +It may not rely on destructors being called. Stick to simple operations like +memcopy. + +Example:: + + #include + #include + #include + #include "try_signal.hpp" + #include + #include + #include + + int main() try + { + int fd = open("test_file", O_RDWR); + void* map = mmap(nullptr, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + + std::vector buf(1024); + std::iota(buf.begin(), buf.end(), 0); + + // disk full or access after EOF are reported as exceptions + sig::try_signal([&]{ + std::memcpy(map, buf.data(), buf.size()); + }); + + munmap(map, 1024); + close(fd); + return 0; + } + catch (std::exception const& e) + { + fprintf(stderr, "exited with exception: %s\n", e.what()); + return 1; + } + diff --git a/deps/try_signal/appveyor.yml b/deps/try_signal/appveyor.yml new file mode 100644 index 0000000..04bd38f --- /dev/null +++ b/deps/try_signal/appveyor.yml @@ -0,0 +1,47 @@ +version: "{build}" +branches: + only: + - master +os: Visual Studio 2015 +clone_depth: 1 +environment: + matrix: + - variant: debug + compiler: msvc-14.0 + model: 64 + - variant: debug + compiler: msvc-14.0 + model: 32 + - variant: release + compiler: msvc-14.0 + model: 64 + - variant: debug + compiler: gcc + model: 32 + - variant: release + compiler: gcc + model: 32 + +install: +- set ROOT_DIRECTORY=%CD% +- set BOOST_ROOT=c:\Libraries\boost_1_67_0 +- set BOOST_BUILD_PATH=%BOOST_ROOT%\tools\build +- echo %BOOST_ROOT% +- echo %BOOST_BUILD_PATH% +- set PATH=%PATH%;%BOOST_BUILD_PATH%\src\engine\bin.ntx86 +- ps: '"using msvc : 14.0 ;`nusing gcc : : : -std=c++11 ;" | Set-Content $env:HOMEDRIVE\$env:HOMEPATH\user-config.jam' +- type %HOMEDRIVE%%HOMEPATH%\user-config.jam +- set PATH=c:\msys64\mingw32\bin;%PATH% +- g++ --version +- python --version +- echo %ROOT_DIRECTORY% +- cd %BOOST_BUILD_PATH%\src\engine +- build.bat >nul +- cd %ROOT_DIRECTORY% + +build_script: +# examples +- b2.exe warnings-as-errors=on -j2 %compiler% address-model=%model% variant=%variant% stage_test + +test_script: +- test diff --git a/deps/try_signal/example.cpp b/deps/try_signal/example.cpp new file mode 100644 index 0000000..703b817 --- /dev/null +++ b/deps/try_signal/example.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +#include "try_signal.hpp" +#include +#include +#include + +int main() try +{ + int fd = open("test_file", O_RDWR); + void* map = mmap(nullptr, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + + std::vector buf(1024); + std::iota(buf.begin(), buf.end(), 0); + + // disk full or access after EOF are reported as exceptions + sig::try_signal([&]{ + std::memcpy(map, buf.data(), buf.size()); + }); + + munmap(map, 1024); + close(fd); + return 0; +} +catch (std::exception const& e) +{ + fprintf(stderr, "exited with exception: %s\n", e.what()); + return 1; +} + diff --git a/deps/try_signal/project-root.jam b/deps/try_signal/project-root.jam new file mode 100644 index 0000000..e69de29 diff --git a/deps/try_signal/signal_error_code.cpp b/deps/try_signal/signal_error_code.cpp new file mode 100644 index 0000000..ae016cf --- /dev/null +++ b/deps/try_signal/signal_error_code.cpp @@ -0,0 +1,209 @@ +/* + +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 "signal_error_code.hpp" + +namespace { + + struct signal_error_category : std::error_category + { + const char* name() const noexcept override + { return "signal"; } + std::string message(int ev) const noexcept override + { +#define SIGNAL_CASE(x) case sig::errors::error_code_enum:: x: return #x; + switch (ev) + { + SIGNAL_CASE(abort) + SIGNAL_CASE(alarm) + SIGNAL_CASE(arithmetic_exception) + SIGNAL_CASE(hangup) + SIGNAL_CASE(illegal) + SIGNAL_CASE(interrupt) + SIGNAL_CASE(kill) + SIGNAL_CASE(pipe) + SIGNAL_CASE(quit) + case sig::errors::error_code_enum::segmentation: return "segmentation fault"; + SIGNAL_CASE(terminate) + SIGNAL_CASE(user1) + SIGNAL_CASE(user2) + SIGNAL_CASE(child) + SIGNAL_CASE(cont) + SIGNAL_CASE(stop) + SIGNAL_CASE(terminal_stop) + SIGNAL_CASE(terminal_in) + SIGNAL_CASE(terminal_out) + SIGNAL_CASE(bus) +#ifdef SIGPOLL + SIGNAL_CASE(poll) +#endif + SIGNAL_CASE(profiler) + SIGNAL_CASE(system_call) + SIGNAL_CASE(trap) + SIGNAL_CASE(urgent_data) + SIGNAL_CASE(virtual_timer) + SIGNAL_CASE(cpu_limit) + SIGNAL_CASE(file_size_limit) + default: return "unknown"; + } +#undef SIGNAL_CASE + } + std::error_condition default_error_condition(int ev) const noexcept override + { return {ev, *this}; } + }; +} // anonymous namespace + +namespace sig { +namespace errors { + + std::error_code make_error_code(error_code_enum e) + { + return {e, sig_category()}; + } + + std::error_condition make_error_condition(error_code_enum e) + { + return {e, sig_category()}; + } + +} // namespace errors + +std::error_category& sig_category() +{ + static signal_error_category signal_category; + return signal_category; +} + +#ifdef _WIN32 + +namespace { + sig::errors::error_code_enum map_exception_code(int const ev) + { + switch (ev) + { + case seh_errors::error_code_enum::access_violation: + case seh_errors::error_code_enum::array_bounds_exceeded: + case seh_errors::error_code_enum::guard_page: + case seh_errors::error_code_enum::stack_overflow: + case seh_errors::error_code_enum::flt_stack_check: + case seh_errors::error_code_enum::in_page_error: + return sig::errors::segmentation; + case seh_errors::error_code_enum::breakpoint: + case seh_errors::error_code_enum::single_step: + return sig::errors::trap; + case seh_errors::error_code_enum::datatype_misalignment: + return sig::errors::bus; + case seh_errors::error_code_enum::flt_denormal_operand: + case seh_errors::error_code_enum::flt_divide_by_zero: + case seh_errors::error_code_enum::flt_inexact_result: + case seh_errors::error_code_enum::flt_invalid_operation: + case seh_errors::error_code_enum::flt_overflow: + case seh_errors::error_code_enum::flt_underflow: + case seh_errors::error_code_enum::int_divide_by_zero: + case seh_errors::error_code_enum::int_overflow: + return sig::errors::arithmetic_exception; + case seh_errors::error_code_enum::illegal_instruction: + case seh_errors::error_code_enum::invalid_disposition: + case seh_errors::error_code_enum::priv_instruction: + case seh_errors::error_code_enum::noncontinuable_exception: + case seh_errors::error_code_enum::status_unwind_consolidate: + return sig::errors::illegal; + case seh_errors::error_code_enum::invalid_handle: + return sig::errors::pipe; + default: + return sig::errors::illegal; + } + } + + struct seh_error_category : std::error_category + { + const char* name() const noexcept override + { return "SEH"; } + std::string message(int ev) const noexcept override + { +#define SIGNAL_CASE(x) case sig::seh_errors::error_code_enum:: x: return #x; + switch (ev) + { + SIGNAL_CASE(access_violation) + SIGNAL_CASE(array_bounds_exceeded) + SIGNAL_CASE(guard_page) + SIGNAL_CASE(stack_overflow) + SIGNAL_CASE(flt_stack_check) + SIGNAL_CASE(in_page_error) + SIGNAL_CASE(breakpoint) + SIGNAL_CASE(single_step) + SIGNAL_CASE(datatype_misalignment) + SIGNAL_CASE(flt_denormal_operand) + SIGNAL_CASE(flt_divide_by_zero) + SIGNAL_CASE(flt_inexact_result) + SIGNAL_CASE(flt_invalid_operation) + SIGNAL_CASE(flt_overflow) + SIGNAL_CASE(flt_underflow) + SIGNAL_CASE(int_divide_by_zero) + SIGNAL_CASE(int_overflow) + SIGNAL_CASE(illegal_instruction) + SIGNAL_CASE(invalid_disposition) + SIGNAL_CASE(priv_instruction) + SIGNAL_CASE(noncontinuable_exception) + SIGNAL_CASE(status_unwind_consolidate) + SIGNAL_CASE(invalid_handle) + default: return "unknown"; + } +#undef SIGNAL_CASE + } + std::error_condition default_error_condition(int ev) const noexcept override + { return std::error_condition(map_exception_code(ev), sig_category()); } + }; +} // anonymous namespace + +namespace seh_errors { + + std::error_code make_error_code(error_code_enum e) + { + return {static_cast(e), seh_category()}; + } + +} // namespace errors + +std::error_category& seh_category() +{ + static seh_error_category seh_category; + return seh_category; +} + +#endif + +} // namespace sig + diff --git a/deps/try_signal/signal_error_code.hpp b/deps/try_signal/signal_error_code.hpp new file mode 100644 index 0000000..27a6c64 --- /dev/null +++ b/deps/try_signal/signal_error_code.hpp @@ -0,0 +1,159 @@ +/* + +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 SIGNAL_ERROR_CODE_HPP_INCLUDED +#define SIGNAL_ERROR_CODE_HPP_INCLUDED + +#include +#include + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#ifdef __GNUC__ +#include +#else +#include +#endif +#endif + +namespace sig { +namespace errors { + +#ifdef _WIN32 +#define SIG_ENUM(name, sig) name, +#else +#define SIG_ENUM(name, sig) name = sig, +#endif + + enum error_code_enum + { + SIG_ENUM(abort, SIGABRT) + SIG_ENUM(alarm, SIGALRM) + SIG_ENUM(arithmetic_exception, SIGFPE) + SIG_ENUM(hangup, SIGHUP) + SIG_ENUM(illegal, SIGILL) + SIG_ENUM(interrupt, SIGINT) + SIG_ENUM(kill, SIGKILL) + SIG_ENUM(pipe, SIGPIPE) + SIG_ENUM(quit, SIGQUIT) + SIG_ENUM(segmentation, SIGSEGV) + SIG_ENUM(terminate, SIGTERM) + SIG_ENUM(user1, SIGUSR1) + SIG_ENUM(user2, SIGUSR2) + SIG_ENUM(child, SIGCHLD) + SIG_ENUM(cont, SIGCONT) + SIG_ENUM(stop, SIGSTOP) + SIG_ENUM(terminal_stop, SIGTSTP) + SIG_ENUM(terminal_in, SIGTTIN) + SIG_ENUM(terminal_out, SIGTTOU) + SIG_ENUM(bus, SIGBUS) +#ifdef SIGPOLL + SIG_ENUM(poll, SIGPOLL) +#endif + SIG_ENUM(profiler, SIGPROF) + SIG_ENUM(system_call, SIGSYS) + SIG_ENUM(trap, SIGTRAP) + SIG_ENUM(urgent_data, SIGURG) + SIG_ENUM(virtual_timer, SIGVTALRM) + SIG_ENUM(cpu_limit, SIGXCPU) + SIG_ENUM(file_size_limit, SIGXFSZ) + }; + +#undef SIG_ENUM + + std::error_code make_error_code(error_code_enum e); + std::error_condition make_error_condition(error_code_enum e); + +} // namespace errors + +std::error_category& sig_category(); + +#ifdef _WIN32 +namespace seh_errors { + + enum error_code_enum + { + access_violation = EXCEPTION_ACCESS_VIOLATION, + array_bounds_exceeded = EXCEPTION_ARRAY_BOUNDS_EXCEEDED, + guard_page = EXCEPTION_GUARD_PAGE, + stack_overflow = EXCEPTION_STACK_OVERFLOW, + flt_stack_check = EXCEPTION_FLT_STACK_CHECK, + in_page_error = EXCEPTION_IN_PAGE_ERROR, + breakpoint = EXCEPTION_BREAKPOINT, + single_step = EXCEPTION_SINGLE_STEP, + datatype_misalignment = EXCEPTION_DATATYPE_MISALIGNMENT, + flt_denormal_operand = EXCEPTION_FLT_DENORMAL_OPERAND, + flt_divide_by_zero = EXCEPTION_FLT_DIVIDE_BY_ZERO, + flt_inexact_result = EXCEPTION_FLT_INEXACT_RESULT, + flt_invalid_operation = EXCEPTION_FLT_INVALID_OPERATION, + flt_overflow = EXCEPTION_FLT_OVERFLOW, + flt_underflow = EXCEPTION_FLT_UNDERFLOW, + int_divide_by_zero = EXCEPTION_INT_DIVIDE_BY_ZERO, + int_overflow = EXCEPTION_INT_OVERFLOW, + illegal_instruction = EXCEPTION_ILLEGAL_INSTRUCTION, + invalid_disposition = EXCEPTION_INVALID_DISPOSITION, + priv_instruction = EXCEPTION_PRIV_INSTRUCTION, + noncontinuable_exception = EXCEPTION_NONCONTINUABLE_EXCEPTION, + status_unwind_consolidate = STATUS_UNWIND_CONSOLIDATE, + invalid_handle = EXCEPTION_INVALID_HANDLE, + }; + + std::error_code make_error_code(error_code_enum e); +} + +std::error_category& seh_category(); + +#endif // _WIN32 + +} // namespace sig + +namespace std +{ +template<> +struct is_error_code_enum : std::true_type {}; + +template<> +struct is_error_condition_enum : std::true_type {}; + +#ifdef _WIN32 +template<> +struct is_error_code_enum : std::true_type {}; +#endif + +} // namespace std + +#endif + diff --git a/deps/try_signal/test.cpp b/deps/try_signal/test.cpp new file mode 100644 index 0000000..b8c67c8 --- /dev/null +++ b/deps/try_signal/test.cpp @@ -0,0 +1,47 @@ +#include +#include +#include // for memcpy + +#include "try_signal.hpp" + +int main() +{ + char const buf[] = "test...test"; + char dest[sizeof(buf)]; + + { + sig::try_signal([&]{ + std::memcpy(dest, buf, sizeof(buf)); + }); + if (!std::equal(buf, buf + sizeof(buf), dest)) { + fprintf(stderr, "ERROR: buffer not copied correctly\n"); + return 1; + } + } + + try { + void* invalid_pointer = nullptr; + sig::try_signal([&]{ + std::memcpy(dest, buf, sizeof(buf)); + std::memcpy(dest, invalid_pointer, sizeof(buf)); + }); + } + catch (std::system_error const& e) + { + if (e.code() != std::error_condition(sig::errors::segmentation)) { + fprintf(stderr, "ERROR: expected segmentaiton violation error\n"); + } + else { + fprintf(stderr, "OK\n"); + } + fprintf(stderr, "exited with expected system_error exception: %s\n", e.what()); + + // we expect this to happen, so return 0 + return e.code() == std::error_condition(sig::errors::segmentation) ? 0 : 1; + } + + // return non-zero here because we don't expect this + fprintf(stderr, "ERROR: expected exit through exception\n"); + return 1; +} + diff --git a/deps/try_signal/try_signal.cpp b/deps/try_signal/try_signal.cpp new file mode 100644 index 0000000..be5cd8d --- /dev/null +++ b/deps/try_signal/try_signal.cpp @@ -0,0 +1,144 @@ +/* + +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 "try_signal.hpp" + +#if !defined _WIN32 +// linux + +namespace sig { +namespace detail { + +namespace { +thread_local sigjmp_buf* jmpbuf = nullptr; +} + +std::atomic_flag once = ATOMIC_FLAG_INIT; + +scoped_jmpbuf::scoped_jmpbuf(sigjmp_buf* ptr) +{ + _previous_ptr = jmpbuf; + jmpbuf = ptr; + std::atomic_signal_fence(std::memory_order_release); +} + +scoped_jmpbuf::~scoped_jmpbuf() { jmpbuf = _previous_ptr; } + +void handler(int const signo, siginfo_t*, void*) +{ + std::atomic_signal_fence(std::memory_order_acquire); + if (jmpbuf) + siglongjmp(*jmpbuf, signo); + + // this signal was not caused within the scope of a try_signal object, + // invoke the default handler + signal(signo, SIG_DFL); + raise(signo); +} + +void setup_handler() +{ + struct sigaction sa; + sa.sa_sigaction = &sig::detail::handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &sa, nullptr); + sigaction(SIGBUS, &sa, nullptr); +} + +} // detail namespace +} // sig namespace + +#elif __GNUC__ +// mingw + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +namespace sig { +namespace detail { + +thread_local jmp_buf* jmpbuf = nullptr; + +long CALLBACK handler(EXCEPTION_POINTERS* pointers) +{ + std::atomic_signal_fence(std::memory_order_acquire); + if (jmpbuf) + longjmp(*jmpbuf, pointers->ExceptionRecord->ExceptionCode); + return EXCEPTION_CONTINUE_SEARCH; +} + +scoped_handler::scoped_handler(jmp_buf* ptr) +{ + _previous_ptr = jmpbuf; + jmpbuf = ptr; + std::atomic_signal_fence(std::memory_order_release); + _handle = AddVectoredExceptionHandler(1, sig::detail::handler); +} +scoped_handler::~scoped_handler() +{ + RemoveVectoredExceptionHandler(_handle); + jmpbuf = _previous_ptr; +} + +} // detail namespace +} // sig namespace + +#else +// windows + +#include // for EXCEPTION_* + +namespace sig { +namespace detail { + + // these are the kinds of SEH exceptions we'll translate into C++ exceptions + bool catch_error(int const code) + { + return code == EXCEPTION_IN_PAGE_ERROR + || code == EXCEPTION_ACCESS_VIOLATION + || code == EXCEPTION_ARRAY_BOUNDS_EXCEEDED; + } +} // detail namespace +} // namespace sig + +#endif // _WIN32 + + diff --git a/deps/try_signal/try_signal.hpp b/deps/try_signal/try_signal.hpp new file mode 100644 index 0000000..557d92b --- /dev/null +++ b/deps/try_signal/try_signal.hpp @@ -0,0 +1,49 @@ +/* + +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 TRY_SIGNAL_HPP_INCLUDED +#define TRY_SIGNAL_HPP_INCLUDED + +#if !defined _WIN32 +// linux +#include "try_signal_posix.hpp" +#elif __GNUC__ +// mingw +#include "try_signal_mingw.hpp" +#else +// windows +#include "try_signal_msvc.hpp" +#endif + + +#endif // TRY_SIGNAL_HPP_INCLUDED + diff --git a/deps/try_signal/try_signal_mingw.hpp b/deps/try_signal/try_signal_mingw.hpp new file mode 100644 index 0000000..e4db043 --- /dev/null +++ b/deps/try_signal/try_signal_mingw.hpp @@ -0,0 +1,78 @@ +/* + +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 TRY_SIGNAL_MINGW_HPP_INCLUDED +#define TRY_SIGNAL_MINGW_HPP_INCLUDED + +#include "signal_error_code.hpp" + +#include // for jmp_buf + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +namespace sig { +namespace detail { + +struct scoped_handler +{ + scoped_handler(jmp_buf* ptr); + ~scoped_handler(); + scoped_handler(scoped_handler const&) = delete; + scoped_handler& operator=(scoped_handler const&) = delete; +private: + void* _handle; + jmp_buf* _previous_ptr; +}; + +} // detail namespace + +template +void try_signal(Fun&& f) +{ + jmp_buf buf; + int const code = setjmp(buf); + // set the thread local jmpbuf pointer, and make sure it's cleared when we + // leave the scope + sig::detail::scoped_handler scope(&buf); + if (code != 0) + throw std::system_error(std::error_code(code, seh_category())); + + f(); +} + +} // sig namespace + +#endif + diff --git a/deps/try_signal/try_signal_msvc.hpp b/deps/try_signal/try_signal_msvc.hpp new file mode 100644 index 0000000..04cc62d --- /dev/null +++ b/deps/try_signal/try_signal_msvc.hpp @@ -0,0 +1,61 @@ +/* + +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 TRY_SIGNAL_MSVC_HPP_INCLUDED +#define TRY_SIGNAL_MSVC_HPP_INCLUDED + +#include "signal_error_code.hpp" + +namespace sig { +namespace detail { + +bool catch_error(int const code); + +} // detail namespace + +template +void try_signal(Fun&& f) +{ + __try + { + f(); + } + __except (detail::catch_error(GetExceptionCode())) + { + throw std::system_error(std::error_code(GetExceptionCode(), seh_category())); + } +} + +} // sig namespace + +#endif + diff --git a/deps/try_signal/try_signal_posix.hpp b/deps/try_signal/try_signal_posix.hpp new file mode 100644 index 0000000..2c4615d --- /dev/null +++ b/deps/try_signal/try_signal_posix.hpp @@ -0,0 +1,82 @@ +/* + +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 TRY_SIGNAL_POSIX_HPP_INCLUDED +#define TRY_SIGNAL_POSIX_HPP_INCLUDED + +#include "signal_error_code.hpp" +#include // for sigjmp_buf +#include + +namespace sig { + +namespace detail { + +extern std::atomic_flag once; + +struct scoped_jmpbuf +{ + explicit scoped_jmpbuf(sigjmp_buf* ptr); + ~scoped_jmpbuf(); + scoped_jmpbuf(scoped_jmpbuf const&) = delete; + scoped_jmpbuf& operator=(scoped_jmpbuf const&) = delete; +private: + sigjmp_buf* _previous_ptr; +}; + +void handler(int const signo, siginfo_t* si, void*); +void setup_handler(); + +} // detail namespace + +template +void try_signal(Fun&& f) +{ + if (sig::detail::once.test_and_set() == false) { + sig::detail::setup_handler(); + } + + sigjmp_buf buf; + int const sig = sigsetjmp(buf, 1); + // set the thread local jmpbuf pointer, and make sure it's cleared when we + // leave the scope + sig::detail::scoped_jmpbuf scope(&buf); + if (sig != 0) + throw std::system_error(static_cast(sig)); + + f(); +} + +} + +#endif + diff --git a/docs/2020 Q4 Mozilla Libtorrent Report Public Report.pdf b/docs/2020 Q4 Mozilla Libtorrent Report Public Report.pdf new file mode 100644 index 0000000000000000000000000000000000000000..04ae5d0f00d52510749788631f3395097821e8a5 GIT binary patch literal 747116 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
{jdCi!L`$J|1-?Yz-U*hN^xr_Ly=o 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≤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%0AegOqrVQXrdPM<`_JSg=nt$BnQ16yv?m9Ky><{ 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�VCs1#l1DVg8EKH+;q; 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≠`Cz<4CeQO~Y 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/Z9(!M3$1HJLJ|P(Hyl8)_ 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

tBIWmMBC|ktVt~Rw|%6s{c##n!8+q9{9b7r^J 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 U(9f4Q|IEItt~>~/user-config.jam + b2 crypto=openssl cxxstd=14 release + +Mac OS:: + + brew install boost-build boost openssl@1.1 + echo "using darwin ;" >>~/user-config.jam + b2 crypto=openssl cxxstd=14 release + +Windows (assuming the boost package is saved to ``C:\boost_1_69_0``):: + + set BOOST_ROOT=c:\boost_1_69_0 + set BOOST_BUILD_PATH=%BOOST_ROOT%\tools\build + (cd %BOOST_ROOT% && .\bootstrap.bat) + echo using msvc ; >>%HOMEDRIVE%%HOMEPATH%\user-config.jam + %BOOST_ROOT%\b2.exe --hash cxxstd=14 release + +docker file +~~~~~~~~~~~ + +A Docker file is available that's used to build and run the fuzzers, at +OSS-Fuzz__. + +__ https://github.com/google/oss-fuzz/tree/master/projects/libtorrent + +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.67 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_69_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_69_0\tools\build``. + +To set an environment variable in windows, type for example:: + + set BOOST_BUILD_PATH=c:\boost_1_69_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 14.2 (2019), just +add a line:: + + using msvc : 14.2 ; + +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 `boost-build documentation`_. + +.. _`boost-build documentation`: https://boostorg.github.io/build/ + + +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_69_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.2 + 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. + +.. note:: Also see the `Visual Studio versions`_. + +.. _`Visual Studio versions`: https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering + +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.2 variant=release link=static runtime-link=static debug-symbols=on + +.. 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:: + + 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_69_0``). In the windows environment, they should have the typical +windows format (``c:/boost_1_69_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`_. + +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. + +The option to link with wolfSSL (by setting the ``crypto`` feature to +``wolfssl``), requires a custom build of wolfSSL using the following +options: ``--enable-asio --enable-sni --enable-nginx``. + +To customize the library path and include path for wolfSSL, set the features +``wolfssl-lib`` and ``wolfssl-include`` respectively. + +To disable linking against any SSL library, set the ``crypto`` build feature to +``built-in``. This will use an embedded version if SHA-1. + +Build features +~~~~~~~~~~~~~~ + ++--------------------------+----------------------------------------------------+ +| boost build feature | values | ++==========================+====================================================+ +| ``cxxstd`` | The version of C++ to use, e.g. ``11``, ``14``, | +| | ``17``, ``20``. The C++ version *may* affect the | +| | libtorrent ABI (the ambition is to avoid that). | ++--------------------------+----------------------------------------------------+ +| ``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. | ++--------------------------+----------------------------------------------------+ +| ``lto`` | * ``on`` - enables link time optimization, also | +| | known as whole program optimization. | ++--------------------------+----------------------------------------------------+ +| ``alert-msg`` | * ``on`` - (default) return human readable | +| | messages from the ``alert::message()`` call. | +| | * ``off`` - Always return empty strings from | +| | ``alert::message()``, and save binary size. | ++--------------------------+----------------------------------------------------+ +| ``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`` | * ``openssl`` - (default) links against openssl | +| | and libcrypto to use for SHA-1 hashing. | +| | This also enables HTTPS-tracker support and | +| | support for bittorrent over SSL. | +| | * ``built-in`` - (default) uses built-in SHA-1 | +| | implementation. In macOS/iOS it uses | +| | CommonCrypto SHA-1 implementation. | +| | * ``wolfssl`` - links against wolfssl to use it | +| | for SHA-1 hashing and HTTPS tracker support. | +| | * ``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. | ++--------------------------+----------------------------------------------------+ +| ``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). | ++--------------------------+----------------------------------------------------+ +| ``mmap-disk-io`` | * ``on`` - default. Enable mmap disk storage (if | +| | available. | +| | * ``off`` - disable mmap storage, and fall back to | +| | single-threaded, portable file operations. | ++--------------------------+----------------------------------------------------+ + +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. + + +Custom build flags +~~~~~~~~~~~~~~~~~~ + +Custom build flags can be passed to the command line via the ``cflags``, +``cxxflags`` and ``linkflags`` features. When specifying custom flags, make sure +to build everything from scratch, to not accidentally mix incompatible flags. +Example:: + + b2 cxxflags=-msse4.1 + +Custom flags can also be configured in the toolset, in ``~/user-config.jam``, +``Jamroot.jam`` or ``project-config.jam``. Example:: + + using gcc : sse41 : g++ : -msse4.1 ; + + +Cross compiling +~~~~~~~~~~~~~~~ + +To cross compile libtorrent, configure a new toolset for ``b2`` to use. Toolsets +can be configured in ``~/user-config.jam``, ``Jamroot.jam`` or +``project-config.jam``. The last two live in the libtorrent root directory. + +A toolset configuration is in this form: + +.. parsed-literal:: + + using *toolset* : *version* : *command-line* : *features* ; + +Toolset is essentially the family of compiler you're setting up, choose from `this list`__. + +__ https://boostorg.github.io/build/manual/master/index.html#bbv2.reference.tools.compilers + +Perhaps the most common ones would be ``gcc``, ``clang``, ``msvc`` and +``darwin`` (Apple's version of clang). + +The version can be left empty to be auto configured, or a custom name can be +used to identify this toolset. + +The *command-line* is what to execute to run the compiler. This is also an +opportunity to insert a call to ``ccache`` for example. + +*features* are boost-build features. Typical features to set here are +````, ```` and ````. For the ``gcc`` toolset, +the ```` can be set to specify which tool to use to create a static +library/archive. This is especially handy when cross compiling. + +Here's an example toolset for cross compiling for ARM Linux:: + + using gcc : arm : arm-linux-gnueabihf-g++ : arm-linux-gnueabihf-ar ; + +To build using this toolset, specify ``gcc-arm`` as the toolset on the ``b2`` command line. For example:: + + b2 toolset=gcc-arm + + +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=14 -G Ninja .. + +The ``CMAKE_CXX_STANDARD`` has to be at least 14, but you may want to raise it +to ``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 + +If you enabled test in the configuration step, to run them, run:: + + ctest + +building with VCPKG +------------------- + +You can download and install libtorrent using the vcpkg_ dependency manager:: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install libtorrent + +.. _vcpkg: https://github.com/Microsoft/vcpkg/ + +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`__ on the vcpkg repository. + +__ https://github.com/Microsoft/vcpkg + +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. Make sure to define the same macros in your +own code that compiles and links with libtorrent. + ++----------------------------------------+-------------------------------------------------+ +| 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_ALERT_MSG`` | Human readable messages returned from the alert | +| | ``message()`` member functions will return | +| | empty strings. | ++----------------------------------------+-------------------------------------------------+ +| ``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_HAVE_MMAP`` | Define as 0 to disable mmap support. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_OPENSSL`` | Link against ``libssl`` for SSL support. Must | +| | be combined with ``TORRENT_USE_LIBCRYPTO`` | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_GNUTLS`` | Link against ``libgnutls`` for SSL support. | ++----------------------------------------+-------------------------------------------------+ +| ``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. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_SSL_PEERS`` | Define to enable support for SSL torrents, | +| | peers are connected over authenticated SSL | +| | streams. | ++----------------------------------------+-------------------------------------------------+ + +.. _`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. + +list of macros +-------------- + +The following is a list of defines that libtorrent is built with: ``BOOST_ALL_NO_LIB``, +``BOOST_ASIO_ENABLE_CANCELIO``, ``BOOST_ASIO_HAS_STD_CHRONO``, +``BOOST_MULTI_INDEX_DISABLE_SERIALIZATION``, ``BOOST_NO_DEPRECATED``, +``BOOST_SYSTEM_NO_DEPRECATED`` + +Make sure you define the same at compile time for your code to avoid any runtime errors +and other issues. + +These might change in the future, so it's always best to verify these every time you +upgrade to a new version of libtorrent. The simplest way to see the full list of macros +defined is to build libtorrent with ``-n -a`` switches added to ``b2`` command line:: + + b2 -n -a toolset=msvc-14.2 link=static runtime-link=static boost-link=static variant=release + +This will output all compiler switches, including defines (such as ``-DBOOST_ASIO_ENABLE_CANCELIO``). diff --git a/docs/client_test.rst b/docs/client_test.rst new file mode 100644 index 0000000..04d4c2e --- /dev/null +++ b/docs/client_test.rst @@ -0,0 +1,49 @@ +=========================== +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:: img/screenshot.png + :class: screenshot + :target: img/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/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..dcdf015 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1,63 @@ +.. 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 (like the rate-based choker). + + Additional fuzzers are also always welcome. Find a libtorrent interface + that's not already covered by a fuzzer (see the ``fuzzers`` directory in the + root) and add a new fuzzer to it. Alternatively, improve an existing fuzzer by + producing inputs that gets coverage deeper in to libtorrent. + +.. _`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`_. + +.. _hacking: hacking.html +.. _`pull request`: https://github.com/arvidn/libtorrent +.. _`todo list`: todo.html +.. _rst: https://docutils.sourceforge.io/rst.html + 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.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.rst b/docs/dht_sec.rst new file mode 100644 index 0000000..67106df --- /dev/null +++ b/docs/dht_sec.rst @@ -0,0 +1,255 @@ +.. 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:: img/ip_id_v4.png + :class: bw +.. image:: img/ip_id_v6.png + :class: bw + +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:: img/complete_bit_prefixes.png + :class: bw + +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:: img/hash_distribution.png + :class: bw + +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.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/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.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.rst b/docs/features.rst new file mode 100644 index 0000000..f719244 --- /dev/null +++ b/docs/features.rst @@ -0,0 +1,323 @@ +.. 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. + +BitTorrent v2 is supported as of libtorrent 2.0. This replaces the previous +merkle hash tree extension. + +features +======== + +libtorrent is an ongoing project under active development. Its +current state supports and includes the following features: + +BitTorrent v2 +------------- + +Starting with version 2.0, libtorrent supports BitTorrent V2 (as specified in +`BEP 52`_). BitTorrent V2 introduces a new format for .torrent files, which generally +has a smaller info-dict than the original format. The .torrent files still contain +piece hashes by default, but they can also be downloaded from peers. + +1. Files are organized in a directory structure, instead of listing full paths. + Torrents that have a lot of files in deep directory structures will use a lot + less space to represent that structure in a v2 torrent. + +2. Piece hashes are organized in a merkle hash trees per file, and only the + roots of the trees are included in the .torrent file. The actual hashes are + delivered by peers. + +The hash tree allows validating payload received from a peer immediately, down +to 16 kiB blocks. In the original bittorrent protocol a whole piece would have +to be downloaded before it could be validated against the hashes. + +The fact that each file has its own hash tree, and that its leaves are defined +to be 16 kiB, means that files with identical content will always have the same +merkle root. This enables finding matches of the same file across different +torrents. + +The new format for torrent files is compatible with the original torrent file +format, which enables *hybrid* torrents. Such torrents that can be used both as +V1 and V2 and will have two swarms, one with V1 and V2 clients and one with only +V2 clients. + +Another major feature of the BitTorrent V2 protocol is that the SHA-1 hash +function has been replaced by SHA-256. + +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`_. +* 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. +* queues torrents for file check, instead of checking all of them in parallel. + 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. + +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, PCP 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 52`: https://www.bittorrent.org/beps/bep_0052.html +.. _`BEP 53`: https://www.bittorrent.org/beps/bep_0053.html +.. _`extension protocol`: extension_protocol.html + +highlighted features +==================== + +disk I/O +-------- + +All disk I/O in libtorrent is done asynchronously to the network thread, by the +disk io threads. Files are mapped into memory and the kernel's page cache is +relied on for caching disk blocks. This has the advantage that the disk cache +size adapts to global system load and memory pressure, maximizing the cache +without bogging down the whole system. Since memory mapped I/O is inherently +synchronous, files can be accessed from multiple disk I/O threads. + +Similarly, for write requests, blocks are queued in a store-buffer while waiting +to be flushed to disk. Read requests that happen before a block has been +flushed, will short circuit by picking the block from the store buffer. + +Memory mapped files are available on Windows and posix 64 bit systems. When +building on other, simpler platforms, or 32 bits, a simple portable and +single-threaded disk I/O back-end is available, using `fopen()` and `fclose()` +family of functions. + +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:: img/write_disk_buffers.png + :width: 100% + :class: bw + +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:: img/read_disk_buffers.png + :width: 100% + :class: bw + + +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. + +customizable file I/O +--------------------- + +.. image:: img/storage.png + :align: right + :class: bw + +libtorrent's disk I/O 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 disk I/O class can be implemented +(inheriting from the disk_interface) that avoids the unnecessary step of mapping +pieces 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). + +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: + +.. code:: c++ + + #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 = std::make_shared(argv[1]); + lt::torrent_handle h = 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`_. + +.. _`python bindings`: python_binding.html + + +portability +=========== + +libtorrent runs on most major operating systems including: + +* Windows +* macOS +* Linux +* BSD +* Solaris + +It uses Boost.Asio, Boost.Optional, Boost.System, Boost.Multiprecision, +Boost.Pool, Boost.Python (for bindings), Boost.CRC and various +other boost libraries. At least version 1.70 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 requires a C++11 compiler and does not build with the following compilers: + +* GCC older than 5.4 +* Visual Studio older than Visual Studio 15 2017 (aka msvc-14.1) + diff --git a/docs/filter-rst.py b/docs/filter-rst.py new file mode 100644 index 0000000..997433c --- /dev/null +++ b/docs/filter-rst.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 +from __future__ import print_function + +import sys + + +def indent(line): + if line == '': + return None + end = 0 + for c in line: + end += 1 + if " \t" not in c: + return line[:end] + return line + + +start_block = False +filter_indent = None + +for line in open(sys.argv[1]): + + if line == '\n': + continue + + if filter_indent: + if line.startswith(filter_indent): + continue + else: + filter_indent = None + + if line.strip().startswith('.. '): + start_block = True + continue + + if line.endswith('::\n'): + start_block = True + continue + + if start_block: + filter_indent = indent(line) + start_block = False + continue + + sys.stdout.write(line) diff --git a/docs/fuzzing.rst b/docs/fuzzing.rst new file mode 100644 index 0000000..fa760bb --- /dev/null +++ b/docs/fuzzing.rst @@ -0,0 +1,99 @@ +================== +fuzzing libtorrent +================== + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +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 + +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 + +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 + +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/gen_reference_doc.py b/docs/gen_reference_doc.py new file mode 100644 index 0000000..2ec2489 --- /dev/null +++ b/docs/gen_reference_doc.py @@ -0,0 +1,1555 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 +from __future__ import print_function + +import urllib.parse +import glob +import os +import sys + +verbose = '--verbose' in sys.argv +dump = '--dump' in sys.argv +internal = '--internal' in sys.argv +plain_output = '--plain-output' in sys.argv +single_page_output = '--single-page' in sys.argv +if plain_output: + plain_file = open('plain_text_out.txt', 'w+') +in_code = None + +paths = ['include/libtorrent/*.hpp', 'include/libtorrent/kademlia/*.hpp', 'include/libtorrent/extensions/*.hpp'] + +if internal: + paths.append('include/libtorrent/aux_/*.hpp') + +files = [] + +for p in paths: + files.extend(glob.glob(os.path.join('..', p))) + +functions = [] +classes = [] +enums = [] +constants = {} + +# maps filename to overview description +overviews = {} + +# maps names -> URL +symbols = {} + +# some files that need pre-processing to turn symbols into +# links into the reference documentation +preprocess_rst = \ + { + 'manual.rst': 'manual-ref.rst', + 'tuning.rst': 'tuning-ref.rst', + 'tutorial.rst': 'tutorial-ref.rst', + 'features.rst': 'features-ref.rst', + 'upgrade_to_1.2.rst': 'upgrade_to_1.2-ref.rst', + 'upgrade_to_2.0.rst': 'upgrade_to_2.0-ref.rst', + 'settings.rst': 'settings-ref.rst' + } + +# some pre-defined sections from the main manual +symbols = \ + { + "queuing_": "manual-ref.html#queuing", + "fast-resume_": "manual-ref.html#fast-resume", + "storage-allocation_": "manual-ref.html#storage-allocation", + "alerts_": "manual-ref.html#alerts", + "upnp-and-nat-pmp_": "manual-ref.html#upnp-and-nat-pmp", + "http-seeding_": "manual-ref.html#http-seeding", + "metadata-from-peers_": "manual-ref.html#metadata-from-peers", + "magnet-links_": "manual-ref.html#magnet-links", + "ssl-torrents_": "manual-ref.html#ssl-torrents", + "dynamic-loading-of-torrent-files_": "manual-ref.html#dynamic-loading-of-torrent-files", + "session-statistics_": "manual-ref.html#session-statistics", + "peer-classes_": "manual-ref.html#peer-classes" + } + +# parse out names of settings, and add them to the symbols list, to get cross +# references working +with open('../src/settings_pack.cpp') as f: + for line in f: + line = line.strip() + if not line.startswith('SET('): + continue + + name = line.split('(')[1].split(',')[0] + symbols['settings_pack::' + name] = 'reference-Settings.html#' + name + +static_links = \ + { + ".. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html", + ".. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html", + ".. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html", + ".. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html", + ".. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html", + ".. _`rate based choking`: manual-ref.html#rate-based-choking", + ".. _extensions: manual-ref.html#extensions", + } + +anon_index = 0 + +category_mapping = { + 'ed25519.hpp': 'ed25519', + 'session.hpp': 'Session', + 'session_handle.hpp': 'Session', + 'torrent_handle.hpp': 'Torrent Handle', + 'torrent_info.hpp': 'Torrent Info', + 'announce_entry.hpp': 'Trackers', + 'peer_class_type_filter.hpp': 'PeerClass', + 'peer_class.hpp': 'PeerClass', + 'torrent_status.hpp': 'Torrent Status', + 'session_stats.hpp': 'Stats', + 'performance_counters.hpp': 'Stats', + 'read_resume_data.hpp': 'Resume Data', + 'write_resume_data.hpp': 'Resume Data', + 'add_torrent_params.hpp': 'Add Torrent', + 'client_data.hpp': 'Add Torrent', + 'session_status.hpp': 'Session', + 'session_params.hpp': 'Session', + 'error_code.hpp': 'Error Codes', + 'storage_defs.hpp': 'Storage', + 'file_storage.hpp': 'Storage', + 'disk_interface.hpp': 'Custom Storage', + 'disk_observer.hpp': 'Custom Storage', + 'mmap_disk_io.hpp': 'Storage', + 'disabled_disk_io.hpp': 'Storage', + 'posix_disk_io.hpp': 'Storage', + 'extensions.hpp': 'Plugins', + 'ut_metadata.hpp': 'Plugins', + 'ut_pex.hpp': 'Plugins', + 'ut_trackers.hpp': 'Plugins', + 'smart_ban.hpp': 'Plugins', + 'peer_connection_handle.hpp': 'Plugins', + 'create_torrent.hpp': 'Create Torrents', + 'alert.hpp': 'Alerts', + 'alert_types.hpp': 'Alerts', + 'bencode.hpp': 'Bencoding', + 'bdecode.hpp': 'Bdecoding', + 'entry.hpp': 'Bencoding', + 'time.hpp': 'Time', + 'escape_string.hpp': 'Utility', + 'enum_net.hpp': 'Network', + 'socket.hpp': 'Network', + 'address.hpp': 'Network', + 'socket_io.hpp': 'Network', + 'bitfield.hpp': 'Utility', + 'sha1_hash.hpp': 'Utility', + 'hasher.hpp': 'Utility', + 'identify_client.hpp': 'Utility', + 'ip_filter.hpp': 'Filter', + 'session_settings.hpp': 'Settings', + 'settings_pack.hpp': 'Settings', + 'fingerprint.hpp': 'Settings', + 'operations.hpp': 'Alerts', + 'disk_buffer_holder.hpp': 'Custom Storage', + 'alert_dispatcher.hpp': 'Alerts', +} + +category_fun_mapping = { + 'min_memory_usage()': 'Settings', + 'high_performance_seed()': 'Settings', + 'default_disk_io_constructor()': 'Storage', + 'settings_interface': 'Custom Storage', +} + + +def categorize_symbol(name, filename): + f = os.path.split(filename)[1] + + if name.endswith('_category()') \ + or name.endswith('_error_code') \ + or name.endswith('error_code_enum') \ + or name.endswith('errors'): + return 'Error Codes' + + if name in category_fun_mapping: + return category_fun_mapping[name] + + if f in category_mapping: + return category_mapping[f] + + if filename.startswith('libtorrent/kademlia/'): + return 'DHT' + + return 'Core' + + +def suppress_warning(filename, name): + f = os.path.split(filename)[1] + if f != 'alert_types.hpp': + return False + + # if name.endswith('_alert') or name == 'message()': + return True + + # return False + + +def first_item(itr): + for i in itr: + return i + return None + + +def is_visible(desc): + if desc.strip().startswith('hidden'): + return False + if internal: + return True + if desc.strip().startswith('internal'): + return False + return True + + +def highlight_signature(s): + name = s.split('(', 1) + name2 = name[0].split(' ') + if len(name2[-1]) == 0: + return s + + # make the name of the function bold + name2[-1] = '**' + name2[-1] + '** ' + + # if there is a return value, make sure we preserve pointer types + if len(name2) > 1: + name2[0] = name2[0].replace('*', '\\*') + name[0] = ' '.join(name2) + + # we have to escape asterisks, since this is rendered into + # a parsed literal in rst + name[1] = name[1].replace('*', '\\*') + + # we also have to escape colons + name[1] = name[1].replace(':', '\\:') + + # escape trailing underscores + name[1] = name[1].replace('_', '\\_') + + # comments in signatures are italic + name[1] = name[1].replace('/\\*', '*/\\*') + name[1] = name[1].replace('\\*/', '\\*/*') + return '('.join(name) + + +def highlight_name(s): + if '=' in s: + splitter = ' = ' + elif '{' in s: + splitter = '{' + else: + return s + + name = s.split(splitter, 1) + name2 = name[0].split(' ') + if len(name2[-1]) == 0: + return s + + name2[-1] = '**' + name2[-1] + '** ' + name[0] = ' '.join(name2) + return splitter.join(name) + + +def html_sanitize(s): + ret = '' + for i in s: + if i == '<': + ret += '<' + elif i == '>': + ret += '>' + elif i == '&': + ret += '&' + else: + ret += i + return ret + + +def looks_like_namespace(line): + line = line.strip() + if line.startswith('namespace'): + return True + return False + + +def looks_like_blank(line): + line = line.split('//')[0] + line = line.replace('{', '') + line = line.replace('}', '') + line = line.replace('[', '') + line = line.replace(']', '') + line = line.replace(';', '') + line = line.strip() + return len(line) == 0 + + +def looks_like_variable(line): + line = line.split('//')[0] + line = line.strip() + if ' ' not in line and '\t' not in line: + return False + if line.startswith('friend '): + return False + if line.startswith('enum '): + return False + if line.startswith(','): + return False + if line.startswith(':'): + return False + if line.startswith('typedef'): + return False + if line.startswith('using'): + return False + if ' = ' in line: + return True + if line.endswith(';'): + return True + return False + + +def looks_like_constant(line): + line = line.strip() + if line.startswith('inline'): + line = line.split('inline')[1] + line = line.strip() + if not line.startswith('constexpr'): + return False + line = line.split('constexpr')[1] + return looks_like_variable(line) + + +def looks_like_forward_decl(line): + line = line.split('//')[0] + line = line.strip() + if not line.endswith(';'): + return False + if '{' in line: + return False + if '}' in line: + return False + if line.startswith('friend '): + return True + if line.startswith('struct '): + return True + if line.startswith('class '): + return True + return False + + +def looks_like_function(line): + line = line.split('//')[0] + if line.startswith('friend class '): + return False + if line.startswith('friend struct '): + return False + if '::' in line.split('(')[0].split(' ')[-1]: + return False + if line.startswith(','): + return False + if line.startswith(':'): + return False + return '(' in line + + +def parse_function(lno, lines, filename): + + start_paren = 0 + end_paren = 0 + signature = '' + + global orphaned_export + orphaned_export = False + + while lno < len(lines): + line = lines[lno].strip() + lno += 1 + if line.startswith('//'): + continue + + start_paren += line.count('(') + end_paren += line.count(')') + + sig_line = line.replace('TORRENT_EXPORT ', '') \ + .replace('TORRENT_EXTRA_EXPORT', '') \ + .replace('TORRENT_V3_EXPLICIT', '') \ + .replace('TORRENT_COUNTER_NOEXCEPT', '') \ + .split('//')[0].strip() + if signature != '': + sig_line = '\n ' + sig_line + signature += sig_line + if verbose: + print('fun %s' % line) + + if start_paren > 0 and start_paren == end_paren: + if signature[-1] != ';': + # we also need to consume the function body + start_paren = 0 + end_paren = 0 + for i in range(len(signature)): + if signature[i] == '(': + start_paren += 1 + elif signature[i] == ')': + end_paren += 1 + + if start_paren > 0 and start_paren == end_paren: + for k in range(i, len(signature)): + if signature[k] == ':' or signature[k] == '{': + signature = signature[0:k].strip() + break + break + + lno = consume_block(lno - 1, lines) + signature += ';' + ret = [{'file': filename[11:], 'signatures': set([signature]), 'names': set( + [signature.split('(')[0].split(' ')[-1].strip() + '()'])}, lno] + if first_item(ret[0]['names']) == '()': + return [None, lno] + return ret + if len(signature) > 0: + print('\x1b[31mFAILED TO PARSE FUNCTION\x1b[0m %s\nline: %d\nfile: %s' % (signature, lno, filename)) + return [None, lno] + + +def add_desc(line): + # plain output prints just descriptions and filters out c++ code. + # it's used to run spell checker over + if plain_output: + for s in line.split('\n'): + # if the first character is a space, strip it + if len(s) > 0 and s[0] == ' ': + s = s[1:] + global in_code + if in_code is not None and not s.startswith(in_code) and len(s) > 1: + in_code = None + + if s.strip().startswith('.. code::'): + in_code = s.split('.. code::')[0] + '\t' + + # strip out C++ code from the plain text output since it's meant for + # running spell checking over + if not s.strip().startswith('.. ') and in_code is None: + plain_file.write(s + '\n') + + +def parse_class(lno, lines, filename): + start_brace = 0 + end_brace = 0 + + name = '' + funs = [] + fields = [] + enums = [] + state = 'public' + context = '' + class_type = 'struct' + blanks = 0 + decl = '' + + while lno < len(lines): + line = lines[lno].strip() + decl += lines[lno].replace('TORRENT_EXPORT ', '') \ + .replace('TORRENT_EXTRA_EXPORT', '') \ + .replace('TORRENT_V3_EXPLICIT', '') \ + .replace('TORRENT_COUNTER_NOEXCEPT', '').split('{')[0].strip() + if '{' in line: + break + if verbose: + print('class %s' % line) + lno += 1 + + if decl.startswith('class'): + state = 'private' + class_type = 'class' + + name = decl.split(':')[0].replace('class ', '').replace('struct ', '').replace('final', '').strip() + + global orphaned_export + orphaned_export = False + + while lno < len(lines): + line = lines[lno].strip() + lno += 1 + + if line == '': + blanks += 1 + context = '' + continue + + if line.startswith('/*'): + lno = consume_comment(lno - 1, lines) + continue + + if line.startswith('#'): + lno = consume_ifdef(lno - 1, lines, True) + continue + + if 'TORRENT_DEFINE_ALERT' in line: + if verbose: + print('xx %s' % line) + blanks += 1 + continue + if 'TORRENT_DEPRECATED' in line: + if verbose: + print('xx %s' % line) + blanks += 1 + continue + + if line.startswith('//'): + if verbose: + print('desc %s' % line) + + line = line[2:] + if len(line) and line[0] == ' ': + line = line[1:] + context += line + '\n' + continue + + start_brace += line.count('{') + end_brace += line.count('}') + + if line == 'private:': + state = 'private' + elif line == 'protected:': + state = 'protected' + elif line == 'public:': + state = 'public' + + if start_brace > 0 and start_brace == end_brace: + return [{'file': filename[11:], 'enums': enums, 'fields':fields, + 'type': class_type, 'name': name, 'decl': decl, 'fun': funs}, lno] + + if state != 'public' and not internal: + if verbose: + print('private %s' % line) + blanks += 1 + continue + + if start_brace - end_brace > 1: + if verbose: + print('scope %s' % line) + blanks += 1 + continue + + if looks_like_function(line): + current_fun, lno = parse_function(lno - 1, lines, filename) + if current_fun is not None and is_visible(context): + if context == '' and blanks == 0 and len(funs): + funs[-1]['signatures'].update(current_fun['signatures']) + funs[-1]['names'].update(current_fun['names']) + else: + if 'TODO: ' in context: + print('TODO comment in public documentation: %s:%d' % (filename, lno)) + sys.exit(1) + current_fun['desc'] = context + add_desc(context) + if context == '' and not suppress_warning(filename, first_item(current_fun['names'])): + print('WARNING: member function "%s" is not documented: \x1b[34m%s:%d\x1b[0m' + % (name + '::' + first_item(current_fun['names']), filename, lno)) + funs.append(current_fun) + context = '' + blanks = 0 + continue + + if looks_like_variable(line): + if 'constexpr static' in line: + print('ERROR: found "constexpr static", use "static constexpr" instead for consistency!\n%s:%d\n%s' + % (filename, lno, line)) + sys.exit(1) + if verbose: + print('var %s' % line) + if not is_visible(context): + continue + line = line.split('//')[0].strip() + # the name may look like this: + # std::uint8_t fails : 7; + # int scrape_downloaded = -1; + # static constexpr peer_flags_t interesting{0x1}; + n = line.split('=')[0].split('{')[0].strip().split(' : ')[0].split(' ')[-1].split(':')[0].split(';')[0] + if context == '' and blanks == 0 and len(fields): + fields[-1]['names'].append(n) + fields[-1]['signatures'].append(line) + else: + if context == '' and not suppress_warning(filename, n): + print('WARNING: field "%s" is not documented: \x1b[34m%s:%d\x1b[0m' + % (name + '::' + n, filename, lno)) + add_desc(context) + fields.append({'signatures': [line], 'names': [n], 'desc': context}) + context = '' + blanks = 0 + continue + + if line.startswith('enum '): + if verbose: + print('enum %s' % line) + if not is_visible(context): + consume_block(lno - 1, lines) + else: + enum, lno = parse_enum(lno - 1, lines, filename) + if enum is not None: + if 'TODO: ' in context: + print('TODO comment in public documentation: %s:%d' % (filename, lno)) + sys.exit(1) + enum['desc'] = context + add_desc(context) + if context == '' and not suppress_warning(filename, enum['name']): + print('WARNING: enum "%s" is not documented: \x1b[34m%s:%d\x1b[0m' + % (name + '::' + enum['name'], filename, lno)) + enums.append(enum) + context = '' + continue + + context = '' + + if verbose: + if looks_like_forward_decl(line) \ + or looks_like_blank(line) \ + or looks_like_namespace(line): + print('-- %s' % line) + else: + print('?? %s' % line) + + if len(name) > 0: + print('\x1b[31mFAILED TO PARSE CLASS\x1b[0m %s\nfile: %s:%d' % (name, filename, lno)) + return [None, lno] + + +def parse_constant(lno, lines, filename): + line = lines[lno].strip() + if verbose: + print('const %s' % line) + line = line.split('=')[0] + if 'constexpr' in line: + line = line.split('constexpr')[1] + if '{' in line and '}' in line: + line = line.split('{')[0] + t, name = line.strip().rsplit(' ', 1) + return [{'file': filename[11:], 'type': t, 'name': name}, lno + 1] + + +def parse_enum(lno, lines, filename): + start_brace = 0 + end_brace = 0 + global anon_index + + line = lines[lno].strip() + name = line.replace('enum ', '').replace('class ', '').split(':')[0].split('{')[0].strip() + if len(name) == 0: + if not internal: + print('WARNING: anonymous enum at: \x1b[34m%s:%d\x1b[0m' % (filename, lno)) + lno = consume_block(lno - 1, lines) + return [None, lno] + name = 'anonymous_enum_%d' % anon_index + anon_index += 1 + + values = [] + context = '' + if '{' not in line: + if verbose: + print('enum %s' % lines[lno]) + lno += 1 + + val = 0 + while lno < len(lines): + line = lines[lno].strip() + lno += 1 + + if line.startswith('//'): + if verbose: + print('desc %s' % line) + line = line[2:] + if len(line) and line[0] == ' ': + line = line[1:] + context += line + '\n' + continue + + if line.startswith('#'): + lno = consume_ifdef(lno - 1, lines) + continue + + start_brace += line.count('{') + end_brace += line.count('}') + + if '{' in line: + line = line.split('{')[1] + line = line.split('}')[0] + + if len(line): + if verbose: + print('enumv %s' % lines[lno - 1]) + for v in line.split(','): + v = v.strip() + if v.startswith('//'): + break + if v == '': + continue + valstr = '' + try: + if '=' in v: + val = int(v.split('=')[1].strip(), 0) + valstr = str(val) + except Exception: + pass + + if '=' in v: + v = v.split('=')[0].strip() + if is_visible(context): + add_desc(context) + values.append({'name': v.strip(), 'desc': context, 'val': valstr}) + if verbose: + print('enumv %s' % valstr) + context = '' + val += 1 + else: + if verbose: + print('?? %s' % lines[lno - 1]) + + if start_brace > 0 and start_brace == end_brace: + return [{'file': filename[11:], 'name': name, 'values': values}, lno] + + if len(name) > 0: + print('\x1b[31mFAILED TO PARSE ENUM\x1b[0m %s\nline: %d\nfile: %s' % (name, lno, filename)) + return [None, lno] + + +def consume_block(lno, lines): + start_brace = 0 + end_brace = 0 + + while lno < len(lines): + line = lines[lno].strip() + if verbose: + print('xx %s' % line) + lno += 1 + + start_brace += line.count('{') + end_brace += line.count('}') + + if start_brace > 0 and start_brace == end_brace: + break + return lno + + +def consume_comment(lno, lines): + while lno < len(lines): + line = lines[lno].strip() + if verbose: + print('xx %s' % line) + lno += 1 + if '*/' in line: + break + + return lno + + +def trim_define(line): + return line.replace('#ifndef', '').replace('#ifdef', '') \ + .replace('#if', '').replace('defined', '') \ + .replace('TORRENT_ABI_VERSION == 1', '') \ + .replace('TORRENT_ABI_VERSION <= 2', '') \ + .replace('TORRENT_ABI_VERSION < 3', '') \ + .replace('||', '').replace('&&', '').replace('(', '').replace(')', '') \ + .replace('!', '').replace('\\', '').strip() + + +def consume_ifdef(lno, lines, warn_on_ifdefs=False): + line = lines[lno].strip() + lno += 1 + + start_if = 1 + end_if = 0 + + if verbose: + print('prep %s' % line) + + if warn_on_ifdefs and line.strip().startswith('#if'): + while line.endswith('\\'): + lno += 1 + line += lines[lno].strip() + if verbose: + print('prep %s' % lines[lno].trim()) + define = trim_define(line) + if 'TORRENT_' in define and 'TORRENT_ABI_VERSION' not in define: + print('\x1b[31mWARNING: possible ABI breakage in public struct! "%s" \x1b[34m %s:%d\x1b[0m' % + (define, filename, lno)) + elif define != '': + print('\x1b[33msensitive define in public struct: "%s"\x1b[34m %s:%d\x1b[0m' % (define, filename, lno)) + + if (line.startswith('#if') and ( + ' TORRENT_USE_ASSERTS' in line or + ' TORRENT_USE_INVARIANT_CHECKS' in line or + ' TORRENT_ASIO_DEBUGGING' in line) or + line == '#if TORRENT_ABI_VERSION == 1' or + line == '#if TORRENT_ABI_VERSION <= 2' or + line == '#if TORRENT_ABI_VERSION < 3'): + while lno < len(lines): + line = lines[lno].strip() + lno += 1 + if verbose: + print('prep %s' % line) + if line.startswith('#endif'): + end_if += 1 + if line.startswith('#if'): + start_if += 1 + if line == '#else' and start_if - end_if == 1: + break + if start_if - end_if == 0: + break + return lno + else: + while line.endswith('\\') and lno < len(lines): + line = lines[lno].strip() + lno += 1 + if verbose: + print('prep %s' % line) + + return lno + + +for filename in files: + h = open(filename) + lines = h.read().split('\n') + + if verbose: + print('\n=== %s ===\n' % filename) + + blanks = 0 + lno = 0 + orphaned_export = False + + while lno < len(lines): + line = lines[lno].strip() + + if orphaned_export: + print('ERROR: TORRENT_EXPORT without function or class!\n%s:%d\n%s' % (filename, lno, line)) + sys.exit(1) + + lno += 1 + + if line == '': + blanks += 1 + context = '' + continue + + if 'TORRENT_EXPORT' in line.split() \ + and 'ifndef TORRENT_EXPORT' not in line \ + and 'define TORRENT_DEPRECATED_EXPORT TORRENT_EXPORT' not in line \ + and 'define TORRENT_EXPORT' not in line \ + and 'for TORRENT_EXPORT' not in line \ + and 'TORRENT_EXPORT TORRENT_CFG' not in line \ + and 'extern TORRENT_EXPORT ' not in line \ + and 'struct TORRENT_EXPORT ' not in line: + orphaned_export = True + if verbose: + print('maybe orphaned: %s\n' % line) + + if line.startswith('//') and line[2:].strip() == 'OVERVIEW': + # this is a section overview + current_overview = '' + while lno < len(lines): + line = lines[lno].strip() + lno += 1 + if not line.startswith('//'): + # end of overview + overviews[filename[11:]] = current_overview + current_overview = '' + break + line = line[2:] + if line.startswith(' '): + line = line[1:] + current_overview += line + '\n' + + if line.startswith('//'): + if verbose: + print('desc %s' % line) + line = line[2:] + if len(line) and line[0] == ' ': + line = line[1:] + context += line + '\n' + continue + + if line.startswith('/*'): + lno = consume_comment(lno - 1, lines) + continue + + if line.startswith('#'): + lno = consume_ifdef(lno - 1, lines) + continue + + if (line == 'namespace aux {' or + line == 'namespace ssl {' or + line == 'namespace libtorrent { namespace aux {') \ + and not internal: + lno = consume_block(lno - 1, lines) + context = '' + continue + + if 'namespace aux' in line and \ + '//' not in line.split('namespace')[0] and \ + '}' not in line.split('namespace')[1]: + print('ERROR: whitespace preceding namespace declaration: %s:%d' % (filename, lno)) + sys.exit(1) + + if 'TORRENT_DEPRECATED' in line: + if ('class ' in line or 'struct ' in line) and ';' not in line: + lno = consume_block(lno - 1, lines) + context = '' + blanks += 1 + if verbose: + print('xx %s' % line) + continue + + if looks_like_constant(line): + if 'constexpr static' in line: + print('ERROR: found "constexpr static", use "static constexpr" instead for consistency!\n%s:%d\n%s' + % (filename, lno, line)) + sys.exit(1) + current_constant, lno = parse_constant(lno - 1, lines, filename) + if current_constant is not None and is_visible(context): + if 'TODO: ' in context: + print('TODO comment in public documentation: %s:%d' % (filename, lno)) + sys.exit(1) + current_constant['desc'] = context + add_desc(context) + if context == '': + print('WARNING: constant "%s" is not documented: \x1b[34m%s:%d\x1b[0m' + % (current_constant['name'], filename, lno)) + t = current_constant['type'] + if t in constants: + constants[t].append(current_constant) + else: + constants[t] = [current_constant] + continue + + if 'TORRENT_EXPORT ' in line or line.startswith('inline ') or line.startswith('template') or internal: + if line.startswith('class ') or line.startswith('struct '): + if not line.endswith(';'): + current_class, lno = parse_class(lno - 1, lines, filename) + if current_class is not None and is_visible(context): + if 'TODO: ' in context: + print('TODO comment in public documentation: %s:%d' % (filename, lno)) + sys.exit(1) + current_class['desc'] = context + add_desc(context) + if context == '': + print('WARNING: class "%s" is not documented: \x1b[34m%s:%d\x1b[0m' + % (current_class['name'], filename, lno)) + classes.append(current_class) + context = '' + blanks += 1 + continue + + if looks_like_function(line): + current_fun, lno = parse_function(lno - 1, lines, filename) + if current_fun is not None and is_visible(context): + if context == '' and blanks == 0 and len(functions): + functions[-1]['signatures'].update(current_fun['signatures']) + functions[-1]['names'].update(current_fun['names']) + else: + if 'TODO: ' in context: + print('TODO comment in public documentation: %s:%d' % (filename, lno)) + sys.exit(1) + current_fun['desc'] = context + add_desc(context) + if context == '': + print('WARNING: function "%s" is not documented: \x1b[34m%s:%d\x1b[0m' + % (first_item(current_fun['names']), filename, lno)) + functions.append(current_fun) + context = '' + blanks = 0 + continue + + if ('enum class ' not in line and 'class ' in line or 'struct ' in line) and ';' not in line: + lno = consume_block(lno - 1, lines) + context = '' + blanks += 1 + continue + + if line.startswith('enum '): + if not is_visible(context): + consume_block(lno - 1, lines) + else: + current_enum, lno = parse_enum(lno - 1, lines, filename) + if current_enum is not None and is_visible(context): + if 'TODO: ' in context: + print('TODO comment in public documentation: %s:%d' % (filename, lno)) + sys.exit(1) + current_enum['desc'] = context + add_desc(context) + if context == '': + print('WARNING: enum "%s" is not documented: \x1b[34m%s:%d\x1b[0m' + % (current_enum['name'], filename, lno)) + enums.append(current_enum) + context = '' + blanks += 1 + continue + + blanks += 1 + if verbose: + if looks_like_forward_decl(line) \ + or looks_like_blank(line) \ + or looks_like_namespace(line): + print('-- %s' % line) + else: + print('?? %s' % line) + + context = '' + h.close() + +# ==================================================================== +# +# RENDER PART +# +# ==================================================================== + + +def new_category(cat): + return {'classes': [], 'functions': [], 'enums': [], + 'filename': 'reference-%s.rst' % cat.replace(' ', '_'), + 'constants': {}} + + +if dump: + + if verbose: + print('\n===============================\n') + + for c in classes: + print('\x1b[4m%s\x1b[0m %s\n{' % (c['type'], c['name'])) + for f in c['fun']: + for s in f['signatures']: + print(' %s' % s.replace('\n', '\n ')) + + if len(c['fun']) > 0 and len(c['fields']) > 0: + print('') + + for f in c['fields']: + for s in f['signatures']: + print(' %s' % s) + + if len(c['fields']) > 0 and len(c['enums']) > 0: + print('') + + for e in c['enums']: + print(' \x1b[4menum\x1b[0m %s\n {' % e['name']) + for v in e['values']: + print(' %s' % v['name']) + print(' };') + print('};\n') + + for f in functions: + print('%s' % f['signature']) + + for e in enums: + print('\x1b[4menum\x1b[0m %s\n{' % e['name']) + for v in e['values']: + print(' %s' % v['name']) + print('};') + + for t, c in constants: + print('\x1b[4mconstant\x1b[0m %s %s\n' % (e['type'], e['name'])) + +categories = {} + +for c in classes: + cat = categorize_symbol(c['name'], c['file']) + if cat not in categories: + categories[cat] = new_category(cat) + + if c['file'] in overviews: + categories[cat]['overview'] = overviews[c['file']] + + filename = categories[cat]['filename'].replace('.rst', '.html') + '#' + categories[cat]['classes'].append(c) + symbols[c['name']] = filename + c['name'] + for f in c['fun']: + for n in f['names']: + symbols[n] = filename + n + symbols[c['name'] + '::' + n] = filename + n + + for f in c['fields']: + for n in f['names']: + symbols[c['name'] + '::' + n] = filename + n + + for e in c['enums']: + symbols[e['name']] = filename + e['name'] + symbols[c['name'] + '::' + e['name']] = filename + e['name'] + for v in e['values']: + # symbols[v['name']] = filename + v['name'] + symbols[e['name'] + '::' + v['name']] = filename + v['name'] + symbols[c['name'] + '::' + v['name']] = filename + v['name'] + +for f in functions: + cat = categorize_symbol(first_item(f['names']), f['file']) + if cat not in categories: + categories[cat] = new_category(cat) + + if f['file'] in overviews: + categories[cat]['overview'] = overviews[f['file']] + + for n in f['names']: + symbols[n] = categories[cat]['filename'].replace('.rst', '.html') + '#' + n + categories[cat]['functions'].append(f) + +for e in enums: + cat = categorize_symbol(e['name'], e['file']) + if cat not in categories: + categories[cat] = new_category(cat) + categories[cat]['enums'].append(e) + filename = categories[cat]['filename'].replace('.rst', '.html') + '#' + symbols[e['name']] = filename + e['name'] + for v in e['values']: + symbols[e['name'] + '::' + v['name']] = filename + v['name'] + +for t, c in constants.items(): + for const in c: + cat = categorize_symbol(t, const['file']) + if cat not in categories: + categories[cat] = new_category(cat) + if t not in categories[cat]['constants']: + categories[cat]['constants'][t] = [const] + else: + categories[cat]['constants'][t].append(const) + filename = categories[cat]['filename'].replace('.rst', '.html') + '#' + symbols[t + '::' + const['name']] = filename + t + '::' + const['name'] + symbols[t] = filename + t + + +def print_declared_in(out, o): + out.write('Declared in "%s"\n\n' % print_link(o['file'], 'include/%s' % o['file'])) + print(dump_link_targets(), file=out) + +# returns RST marked up string + + +def linkify_symbols(string): + lines = string.split('\n') + ret = [] + in_literal = False + lno = 0 + return_string = '' + for line in lines: + lno += 1 + # don't touch headlines, i.e. lines whose + # next line entirely contains one of =, - or . + if (lno < len(lines) - 1): + next_line = lines[lno] + else: + next_line = '' + + if '.. include:: ' in line: + return_string += '\n'.join(ret) + ret = [line] + return_string += dump_link_targets() + '\n' + continue + + if len(next_line) > 0 and lines[lno].replace('=', ''). \ + replace('-', '').replace('.', '') == '': + ret.append(line) + continue + + if line.startswith('|'): + ret.append(line) + continue + if in_literal and not line.startswith('\t') and not line == '': + # print(' end literal: "%s"' % line) + in_literal = False + if in_literal: + # print(' literal: "%s"' % line) + ret.append(line) + continue + if line.strip() == '.. parsed-literal::' or \ + line.strip().startswith('.. code::') or \ + (not line.strip().startswith('..') and line.endswith('::')): + # print(' start literal: "%s"' % line) + in_literal = True + words = line.split(' ') + + for i in range(len(words)): + # it's important to preserve leading + # tabs, since that's relevant for + # rst markup + + leading = '' + w = words[i] + + if len(w) == 0: + continue + + while len(w) > 0 and \ + w[0] in ['\t', ' ', '(', '[', '{']: + leading += w[0] + w = w[1:] + + # preserve commas and dots at the end + w = w.strip() + trailing = '' + + if len(w) == 0: + continue + + while len(w) > 1 and w[-1] in ['.', ',', ')'] and w[-2:] != '()': + trailing = w[-1] + trailing + w = w[:-1] + + link_name = w + + # print(w) + + if len(w) == 0: + continue + + if link_name[-1] == '_': + link_name = link_name[:-1] + + if w in symbols: + link_name = link_name.replace('-', ' ') + # print(' found %s -> %s' % (w, link_name)) + words[i] = leading + print_link(link_name, symbols[w]) + trailing + ret.append(' '.join(words)) + return_string += '\n'.join(ret) + return return_string + + +link_targets = [] + + +def print_link(name, target): + global link_targets + link_targets.append(target) + return "`%s`__" % name + + +def dump_link_targets(indent=''): + global link_targets + ret = '\n' + for link in link_targets: + ret += '%s__ %s\n' % (indent, link) + link_targets = [] + return ret + + +def heading(string, c, indent=''): + string = string.strip() + return '\n' + indent + string + '\n' + indent + (c * len(string)) + '\n' + + +def render_enums(out, enums, print_declared_reference, header_level): + for e in enums: + print('.. raw:: html\n', file=out) + print('\t' % e['name'], file=out) + print('', file=out) + dump_report_issue('enum ' + e['name'], out) + print(heading('enum %s' % e['name'], header_level), file=out) + + print_declared_in(out, e) + + width = [len('name'), len('value'), len('description')] + + for i in range(len(e['values'])): + e['values'][i]['desc'] = linkify_symbols(e['values'][i]['desc']) + + for v in e['values']: + width[0] = max(width[0], len(v['name'])) + width[1] = max(width[1], len(v['val'])) + for d in v['desc'].split('\n'): + width[2] = max(width[2], len(d)) + + print('+-' + ('-' * width[0]) + '-+-' + ('-' * width[1]) + '-+-' + ('-' * width[2]) + '-+', file=out) + print('| ' + 'name'.ljust(width[0]) + ' | ' + 'value'.ljust(width[1]) + ' | ' + + 'description'.ljust(width[2]) + ' |', file=out) + print('+=' + ('=' * width[0]) + '=+=' + ('=' * width[1]) + '=+=' + ('=' * width[2]) + '=+', file=out) + for v in e['values']: + d = v['desc'].split('\n') + if len(d) == 0: + d = [''] + print('| ' + v['name'].ljust(width[0]) + ' | ' + v['val'].ljust(width[1]) + ' | ' + + d[0].ljust(width[2]) + ' |', file=out) + for s in d[1:]: + print('| ' + (' ' * width[0]) + ' | ' + (' ' * width[1]) + ' | ' + s.ljust(width[2]) + ' |', file=out) + print('+-' + ('-' * width[0]) + '-+-' + ('-' * width[1]) + '-+-' + ('-' * width[2]) + '-+', file=out) + print('', file=out) + + print(dump_link_targets(), file=out) + + +sections = \ + { + 'Core': 0, + 'DHT': 0, + 'Session': 0, + 'Torrent Handle': 0, + 'Torrent Info': 0, + 'Trackers': 0, + 'Settings': 0, + 'Torrent Status': 0, + 'Stats': 0, + 'Resume Data': 0, + 'Add Torrent': 0, + + 'Bencoding': 1, + 'Bdecoding': 1, + 'Filter': 1, + 'Error Codes': 1, + 'Create Torrents': 1, + + 'PeerClass': 2, + 'ed25519': 2, + 'Utility': 2, + 'Storage': 2, + 'Custom Storage': 2, + 'Plugins': 2, + + 'Alerts': 3 + } + + +def print_toc(out, categories, s): + + main_toc = False + + for cat in categories: + if (s != 2 and cat not in sections) or \ + (cat in sections and sections[cat] != s): + continue + + if not main_toc: + out.write('.. container:: main-toc\n\n') + main_toc = True + + print('\t.. rubric:: %s\n' % cat, file=out) + + if 'overview' in categories[cat]: + print('\t| overview__', file=out) + + for c in categories[cat]['classes']: + print('\t| ' + print_link(c['name'], symbols[c['name']]), file=out) + for f in categories[cat]['functions']: + for n in f['names']: + print('\t| ' + print_link(n, symbols[n]), file=out) + for e in categories[cat]['enums']: + print('\t| ' + print_link(e['name'], symbols[e['name']]), file=out) + for t, c in categories[cat]['constants'].items(): + print('\t| ' + print_link(t, symbols[t]), file=out) + print('', file=out) + + if 'overview' in categories[cat]: + print('\t__ %s#overview' % categories[cat]['filename'].replace('.rst', '.html'), file=out) + print(dump_link_targets('\t'), file=out) + + +def dump_report_issue(h, out): + print(('.. raw:: html\n\n\t[report issue]\n\n').format( + urllib.parse.quote_plus(h), + urllib.parse.quote_plus('Documentation under heading "' + h + '" could be improved')), file=out) + + +def render(out, category): + + classes = category['classes'] + functions = category['functions'] + enums = category['enums'] + constants = category['constants'] + + if 'overview' in category: + out.write('%s\n' % linkify_symbols(category['overview'])) + + for c in classes: + + print('.. raw:: html\n', file=out) + print('\t' % c['name'], file=out) + print('', file=out) + + dump_report_issue('class ' + c['name'], out) + out.write('%s\n' % heading(c['name'], '-')) + print_declared_in(out, c) + c['desc'] = linkify_symbols(c['desc']) + out.write('%s\n' % c['desc']) + print(dump_link_targets(), file=out) + + print('\n.. parsed-literal::\n\t', file=out) + + block = '\n%s\n{\n' % c['decl'] + for f in c['fun']: + for s in f['signatures']: + block += ' %s\n' % highlight_signature(s.replace('\n', '\n ')) + + if len(c['fun']) > 0 and len(c['enums']) > 0: + block += '\n' + + first = True + for e in c['enums']: + if not first: + block += '\n' + first = False + block += ' enum %s\n {\n' % e['name'] + for v in e['values']: + block += ' %s,\n' % v['name'] + block += ' };\n' + + if len(c['fun']) + len(c['enums']) > 0 and len(c['fields']): + block += '\n' + + for f in c['fields']: + for s in f['signatures']: + block += ' %s\n' % highlight_name(s) + + block += '};' + + print(block.replace('\n', '\n\t') + '\n', file=out) + + for f in c['fun']: + if f['desc'] == '': + continue + print('.. raw:: html\n', file=out) + for n in f['names']: + print('\t' % n, file=out) + print('', file=out) + h = ' '.join(f['names']) + dump_report_issue('%s::[%s]' % (c['name'], h), out) + print(heading(h, '.'), file=out) + + block = '.. parsed-literal::\n\n' + + for s in f['signatures']: + block += highlight_signature(s.replace('\n', '\n ')) + '\n' + print('%s\n' % block.replace('\n', '\n\t'), file=out) + f['desc'] = linkify_symbols(f['desc']) + print('%s' % f['desc'], file=out) + + print(dump_link_targets(), file=out) + + render_enums(out, c['enums'], False, '.') + + for f in c['fields']: + if f['desc'] == '': + continue + + print('.. raw:: html\n', file=out) + for n in f['names']: + print('\t' % n, file=out) + print('', file=out) + h = ' '.join(f['names']) + dump_report_issue('%s::[%s]' % (c['name'], h), out) + print(h, file=out) + f['desc'] = linkify_symbols(f['desc']) + print('\t%s' % f['desc'].replace('\n', '\n\t'), file=out) + + print(dump_link_targets(), file=out) + + for f in functions: + print('.. raw:: html\n', file=out) + for n in f['names']: + print('\t' % n, file=out) + print('', file=out) + h = ' '.join(f['names']) + dump_report_issue(h, out) + print(heading(h, '-'), file=out) + print_declared_in(out, f) + + block = '.. parsed-literal::\n\n' + for s in f['signatures']: + block += highlight_signature(s) + '\n' + + print('%s\n' % block.replace('\n', '\n\t'), file=out) + print(linkify_symbols(f['desc']), file=out) + + print(dump_link_targets(), file=out) + + render_enums(out, enums, True, '-') + + for t, c in constants.items(): + print('.. raw:: html\n', file=out) + print('\t\n' % t, file=out) + dump_report_issue(t, out) + print(heading(t, '-'), file=out) + print_declared_in(out, c[0]) + + for v in c: + print('.. raw:: html\n', file=out) + print('\t\n' % (t, v['name']), file=out) + print(v['name'], file=out) + v['desc'] = linkify_symbols(v['desc']) + print('\t%s' % v['desc'].replace('\n', '\n\t'), file=out) + print(dump_link_targets('\t'), file=out) + + print('', file=out) + + print(dump_link_targets(), file=out) + + for i in static_links: + print(i, file=out) + + +if single_page_output: + + out = open('single-page-ref.rst', 'w+') + out.write('''.. include:: header.rst + +`home`__ + +__ reference.html + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +''') + + for cat in categories: + render(out, categories[cat]) + + out.close() + +else: + + out = open('reference.rst', 'w+') + out.write('''======================= +reference documentation +======================= + +''') + + out.write('`single-page version`__\n\n__ single-page-ref.html\n\n') + + for i in range(4): + + print_toc(out, categories, i) + + out.close() + + for cat in categories: + out = open(categories[cat]['filename'], 'w+') + + out.write('''.. include:: header.rst + +`home`__ + +__ reference.html + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +''') + + render(out, categories[cat]) + + out.close() + +# for s in symbols: +# print(s) + + for i, o in list(preprocess_rst.items()): + f = open(i, 'r') + out = open(o, 'w+') + print('processing %s -> %s' % (i, o)) + link = linkify_symbols(f.read()) + print(link, end=' ', file=out) + + print(dump_link_targets(), file=out) + + out.close() + f.close() diff --git a/docs/gen_settings_doc.py b/docs/gen_settings_doc.py new file mode 100755 index 0000000..dc0f789 --- /dev/null +++ b/docs/gen_settings_doc.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +from __future__ import print_function + +f = open('../include/libtorrent/settings_pack.hpp') + +out = open('settings.rst', 'w+') +all_names = set() + + +def print_field(str, width): + return '%s%s' % (str, ' ' * (width - len(str))) + + +def render_section(names, description, type, default_values): + max_name_len = max(len(max(names, key=len)), len('name')) + max_type_len = max(len(type), len('type')) + max_val_len = max(len(max(default_values, key=len)), len('default')) + + # add link targets for the rest of the manual to reference + for n in names: + print('.. _%s:\n' % n, file=out) + for w in n.split('_'): + all_names.add(w) + + if len(names) > 0: + print('.. raw:: html\n', file=out) + for n in names: + print('\t' % n, file=out) + print('', file=out) + + separator = '+-' + ('-' * max_name_len) + '-+-' + ('-' * max_type_len) + '-+-' + ('-' * max_val_len) + '-+' + + # build a table for the settings, their type and default value + print(separator, file=out) + print( + '| %s | %s | %s |' % + (print_field( + 'name', max_name_len), print_field( + 'type', max_type_len), print_field( + 'default', max_val_len)), file=out) + print(separator.replace('-', '='), file=out) + for i in range(len(names)): + print( + '| %s | %s | %s |' % + (print_field( + names[i], max_name_len), print_field( + type, max_type_len), print_field( + default_values[i], max_val_len)), file=out) + print(separator, file=out) + print(file=out) + print(description, file=out) + + +mode = '' + +# parse out default values for settings +f2 = open('../src/settings_pack.cpp') +def_map = {} +for line in f2: + line = line.strip() + if not line.startswith('SET(') \ + and not line.startswith('SET_NOPREV(') \ + and not line.startswith('DEPRECATED_SET('): + continue + + line = line.split('(')[1].split(',') + if line[1].strip()[0] == '"': + default = ','.join(line[1:]).strip()[1:].split('"')[0].strip() + else: + default = line[1].strip() + def_map[line[0]] = default + print('%s = %s' % (line[0], default)) + +description = '' +names = [] + +for line in f: + if 'enum string_types' in line: + mode = 'string' + if 'enum bool_types' in line: + mode = 'bool' + if 'enum int_types' in line: + mode = 'int' + if '#if TORRENT_ABI_VERSION == 1' in line: + mode += 'skip' + if '#if TORRENT_ABI_VERSION <= 2' in line: + mode += 'skip' + if '#endif' in line: + mode = mode[0:-4] + + if mode == '': + continue + if mode[-4:] == 'skip': + continue + + line = line.lstrip() + + if line == '' and len(names) > 0: + if description == '': + for n in names: + print('WARNING: no description for "%s"' % n) + else: + default_values = [] + for n in names: + default_values.append(def_map[n]) + render_section(names, description, mode, default_values) + description = '' + names = [] + + if line.startswith('};'): + mode = '' + continue + + if line.startswith('//'): + if line[2] == ' ': + description += line[3:] + else: + description += line[2:] + continue + + line = line.strip() + if line.endswith(','): + line = line[:-1] # strip trailing comma + if '=' in line: + line = line.split('=')[0].strip() + if line.endswith('_internal'): + continue + + names.append(line) + +dictionary = open('hunspell/settings.dic', 'w+') +for w in all_names: + dictionary.write(w + '\n') +dictionary.close() +out.close() +f.close() diff --git a/docs/gen_stats_doc.py b/docs/gen_stats_doc.py new file mode 100755 index 0000000..c32e017 --- /dev/null +++ b/docs/gen_stats_doc.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +from __future__ import print_function + +counter_types = {} + +f = open('../include/libtorrent/performance_counters.hpp') + +counter_type = '' + +for line in f: + + # ignore anything after // + if '//' in line: + line = line.split('//')[0] + + line = line.strip() + + if line.startswith('#'): + continue + if line == '': + continue + + if 'enum stats_counter_t' in line: + counter_type = 'counter' + continue + + if 'enum stats_gauge_t' in line: + counter_type = 'gauge' + continue + + if '{' in line or '}' in line or 'struct' in line or 'namespace' in line: + continue + if counter_type == '': + continue + if not line.endswith(','): + continue + + # strip off trailing comma + line = line[:-1] + if '=' in line: + line = line[:line.index('=')].strip() + + counter_types[line] = counter_type + +f.close() + +f = open('../src/session_stats.cpp') + +out = open('stats_counters.rst', 'w+') + + +def print_field(str, width): + return '%s%s' % (str, ' ' * (width - len(str))) + + +def render_section(names, description, types): + max_name_len = max(len(max(names, key=len)), len('name')) + max_type_len = max(len(max(types, key=len)), len('type')) + + if description == '': + for n in names: + print('WARNING: no description for "%s"' % n) + + # add link targets for the rest of the manual to reference + for n in names: + print('.. _%s:\n' % n, file=out) + + if len(names) > 0: + print('.. raw:: html\n', file=out) + for n in names: + print('\t' % n, file=out) + print('', file=out) + + separator = '+-' + ('-' * max_name_len) + '-+-' + ('-' * max_type_len) + '-+' + + # build a table for the settings, their type and default value + print(separator, file=out) + print('| %s | %s |' % (print_field('name', max_name_len), print_field('type', max_type_len)), file=out) + print(separator.replace('-', '='), file=out) + for i in range(len(names)): + print('| %s | %s |' % (print_field(names[i], max_name_len), print_field(types[i], max_type_len)), file=out) + print(separator, file=out) + print(file=out) + print(description, file=out) + print('', file=out) + + +mode = '' + +description = '' +names = [] +types = [] + +for line in f: + description_line = line.lstrip().startswith('//') + + line = line.strip() + + if mode == 'ignore': + if '#endif' in line: + mode = '' + continue + + if 'TORRENT_ABI_VERSION == 1' in line: + mode = 'ignore' + continue + + if description_line: + if len(names) > 0: + render_section(names, description, types) + description = '' + names = [] + types = [] + + description += '\n' + line[3:] + + if '#define' in line: + continue + + if 'METRIC(' in line: + args = line.split('(')[1].split(')')[0].split(',') + + # args: category, name, type + + args[1] = args[1].strip() + names.append(args[0].strip() + '.' + args[1].strip()) + types.append(counter_types[args[1]]) + +if len(names) > 0: + render_section(names, description, types) + +out.close() +f.close() diff --git a/docs/gen_todo.py b/docs/gen_todo.py new file mode 100755 index 0000000..d188475 --- /dev/null +++ b/docs/gen_todo.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 + +import glob +import os +import sys + +paths = [ + 'test/*.cpp', + 'src/*.cpp', + 'src/kademlia/*.cpp', + 'include/libtorrent/*.hpp', + 'include/libtorrent/kademlia/*.hpp', + 'include/libtorrent/aux_/*.hpp', + 'include/libtorrent/extensions/*.hpp'] + +os.system('(cd .. ; ctags %s 2>/dev/null)' % ' '.join(paths)) + +files = [] + +for p in paths: + files.extend(glob.glob(os.path.join('..', p))) + +items = [] + +# keeps 20 non-comment lines, used to get more context around +# todo-items +context = [] + +priority_count = [0, 0, 0, 0, 0] + + +def html_sanitize(s): + ret = '' + for i in s: + if i == '<': + ret += '<' + elif i == '>': + ret += '>' + elif i == '&': + ret += '&' + else: + ret += i + return ret + + +for f in files: + h = open(f) + + state = '' + line_no = 0 + context_lines = 0 + + for orig_line in h: + line_no += 1 + line = orig_line.strip() + if 'TODO:' in line and line.startswith('//'): + line = line.split('TODO:')[1].strip() + state = 'todo' + items.append({}) + items[-1]['location'] = '%s:%d' % (f, line_no) + items[-1]['priority'] = 0 + if line[0] in '0123456789': + items[-1]['priority'] = int(line[0]) + if int(line[0]) > 5: + print('priority too high: ' + line) + sys.exit(1) + + line = line[1:].strip() + items[-1]['todo'] = line + prio = items[-1]['priority'] + if prio >= 0 and prio <= 4: + priority_count[prio] += 1 + continue + + if state == '': + context.append(html_sanitize(orig_line)) + if len(context) > 20: + context.pop(0) + continue + + if state == 'todo': + if line.strip().startswith('//'): + items[-1]['todo'] += '\n' + items[-1]['todo'] += line[2:].strip() + else: + state = 'context' + items[-1]['context'] = ''.join(context) + \ + '

' + html_sanitize(orig_line) + '
' + context_lines = 1 + + context.append(html_sanitize(orig_line)) + if len(context) > 20: + context.pop(0) + continue + + if state == 'context': + items[-1]['context'] += html_sanitize(orig_line) + context_lines += 1 + + context.append(html_sanitize(orig_line)) + if len(context) > 20: + context.pop(0) + if context_lines > 30: + state = '' + + h.close() + +items.sort(key=lambda x: x['priority'], reverse=True) + +# for i in items: +# print('\n\n', i['todo'], '\n') +# print(i['location'], '\n') +# print('prio: ', i['priority'], '\n') +# if 'context' in i: +# print(i['context'], '\n') + +out = open('todo.html', 'w+') +out.write(''' + + + +

libtorrent todo-list

+%d urgent +%d important +%d relevant +%d feasible +%d notes + ''' # noqa + % (priority_count[4], priority_count[3], priority_count[2], priority_count[1], priority_count[0])) + +prio_colors = ['#ccc', '#ccf', '#cfc', '#fcc', '#f44'] + +index = 0 +for i in items: + if 'context' not in i: + i['context'] = '' + out.write(('' + '') + % (prio_colors[i['priority']], i['priority'], index, i['location'], i['todo'].replace('\n', ' '))) + + out.write( + ('') % + (index, i['todo'], i['location'], i['context'])) + index += 1 + +out.write('
relevance %d%s%s
') +out.close() diff --git a/docs/hacking.rst b/docs/hacking.rst new file mode 100644 index 0000000..ebd15ee --- /dev/null +++ b/docs/hacking.rst @@ -0,0 +1,123 @@ +================== +libtorrent hacking +================== + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +This describe some of the internals of libtorrent. If you're looking for +something to contribute, please take a look at the `todo list`_. + +.. _`todo list`: todo.html + +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: + + +.. image:: img/hacking.png + :class: bw + +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 at least 3 threads, but likely more, depending on the +settings_pack::aio_threads setting. The kinds of threads are: + + * The main network thread that manages all sockets; + sending and receiving messages and maintaining all session, torrent and peer + state. In an idle session, this thread will mostly be blocked in a system call, + waiting for socket activity, such as ``epoll()``. + + * A disk I/O thread. There may be multiple disk threads. All disk read and + write operations are passed to this thread and messages are passed back to + the main thread when the operation completes. This kind of thread also performs + the SHA-1/SHA-256 calculations to verify pieces. Some disk threads may have an + affinity for those jobs, to avoid starvation of the disk. + + * At least one thread is spawned by boost.asio on systems that don't support + asynchronous host name resolution, in order to simulate non-blocking ``getaddrinfo()``. + diff --git a/docs/header.rst b/docs/header.rst new file mode 100644 index 0000000..4cfda99 --- /dev/null +++ b/docs/header.rst @@ -0,0 +1 @@ +:Version: 2.0.6 diff --git a/docs/hunspell/en_US.aff b/docs/hunspell/en_US.aff new file mode 100644 index 0000000..bb2fcbc --- /dev/null +++ b/docs/hunspell/en_US.aff @@ -0,0 +1,466 @@ +SET ISO8859-1 +KEY qwertyuiop|asdfghjkl|zxcvbnm +TRY esianrtolcdugmphbyfvkwzESIANRTOLCDUGMPHBYFVKWZ'- +NOSUGGEST ! + +# ordinal numbers (1st, 2nd, 3th, 11th) and decads (0s, 10s, 1990s) +COMPOUNDMIN 1 +# only in compounds: 1th, 2th, 3th +ONLYINCOMPOUND c +# compound rules: +# 1. [0-9]*1[0-9]th (10th, 11th, 12th, 56714th, etc.) +# 2. [0-9]*[02-9](1st|2nd|3rd|[4-9]th) (21st, 22nd, 123rd, 1234th, etc.) +COMPOUNDRULE 2 +COMPOUNDRULE n*1t +COMPOUNDRULE n*mp +WORDCHARS 0123456789' + +PFX A Y 1 +PFX A 0 re . + +PFX I Y 1 +PFX I 0 in . + +PFX U Y 1 +PFX U 0 un . + +PFX C Y 1 +PFX C 0 de . + +PFX E Y 1 +PFX E 0 dis . + +PFX F Y 1 +PFX F 0 con . + +PFX K Y 1 +PFX K 0 pro . + +SFX V N 2 +SFX V e ive e +SFX V 0 ive [^e] + +SFX N Y 3 +SFX N e ion e +SFX N y ication y +SFX N 0 en [^ey] + +SFX X Y 3 +SFX X e ions e +SFX X y ications y +SFX X 0 ens [^ey] + +SFX H N 2 +SFX H y ieth y +SFX H 0 th [^y] + +SFX Y Y 1 +SFX Y 0 ly . + +SFX G Y 2 +SFX G e ing e +SFX G 0 ing [^e] + +SFX J Y 2 +SFX J e ings e +SFX J 0 ings [^e] + +SFX D Y 4 +SFX D 0 d e +SFX D y ied [^aeiou]y +SFX D 0 ed [^ey] +SFX D 0 ed [aeiou]y + +SFX T N 4 +SFX T 0 st e +SFX T y iest [^aeiou]y +SFX T 0 est [aeiou]y +SFX T 0 est [^ey] + +SFX R Y 4 +SFX R 0 r e +SFX R y ier [^aeiou]y +SFX R 0 er [aeiou]y +SFX R 0 er [^ey] + +SFX Z Y 4 +SFX Z 0 rs e +SFX Z y iers [^aeiou]y +SFX Z 0 ers [aeiou]y +SFX Z 0 ers [^ey] + +SFX S Y 4 +SFX S y ies [^aeiou]y +SFX S 0 s [aeiou]y +SFX S 0 es [sxzh] +SFX S 0 s [^sxzhy] + +SFX P Y 3 +SFX P y iness [^aeiou]y +SFX P 0 ness [aeiou]y +SFX P 0 ness [^y] + +SFX M Y 1 +SFX M 0 's . + +SFX B Y 3 +SFX B 0 able [^aeiou] +SFX B 0 able ee +SFX B e able [^aeiou]e + +SFX L Y 1 +SFX L 0 ment . + +REP 97 +REP nt n't +REP alot a_lot +REP avengence a_vengeance +REP ninties 1990s +REP teached taught +REP rised rose +REP a ei +REP ei a +REP a ey +REP ey a +REP ai ie +REP ie ai +REP are air +REP are ear +REP are eir +REP air are +REP air ere +REP ere air +REP ere ear +REP ere eir +REP ear are +REP ear air +REP ear ere +REP eir are +REP eir ere +REP ch te +REP te ch +REP ch ti +REP ti ch +REP ch tu +REP tu ch +REP ch s +REP s ch +REP ch k +REP k ch +REP f ph +REP ph f +REP gh f +REP f gh +REP i igh +REP igh i +REP i uy +REP uy i +REP i ee +REP ee i +REP j di +REP di j +REP j gg +REP gg j +REP j ge +REP ge j +REP s ti +REP ti s +REP s ci +REP ci s +REP k cc +REP cc k +REP k qu +REP qu k +REP kw qu +REP o eau +REP eau o +REP o ew +REP ew o +REP oo ew +REP ew oo +REP ew ui +REP ui ew +REP oo ui +REP ui oo +REP ew u +REP u ew +REP oo u +REP u oo +REP u oe +REP oe u +REP u ieu +REP ieu u +REP ue ew +REP ew ue +REP uff ough +REP oo ieu +REP ieu oo +REP ier ear +REP ear ier +REP ear air +REP air ear +REP w qu +REP qu w +REP z ss +REP ss z +REP shun tion +REP shun sion +REP shun cion +REP tion ssion +REP ys ies +REP u ough + +# PHONEtic_english.h - #PHONEtic transformation rules for use with #PHONEtic.c +# Copyright (C) 2000 Björn Jacke +# +# This rule set is based on Lawrence Phillips original metaPHONE +# algorithm with modifications made by Michael Kuhn in his +# C implantation, more modifications by Björn Jacke when +# converting the algorithm to a rule set and minor +# touch ups by Kevin Atkinson +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License version 2.1 as published by the Free Software Foundation; +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Björn Jacke may be reached by email at bjoern.jacke@gmx.de +# +# Changelog: +# +# 2000-01-05 Björn Jacke +# - first version with translation rules derived from +# metaPHONE.cc distributed with aspell 0.28.3 +# - "TH" is now representated as "@" because "0" is a +# meta character +# - removed TH(!vowel) --> T; always use TH --> # instead +# - dropped "^AE" -> "E" (redundant) +# - "ing" is transformed to "N", not "NK" +# - "SCH(EO)" transforms to "SK" now +# - added R --> SILENT if (after a vowel) and no (vowel or +# "y" follows) like in "Marcy" or "abort" +# - H is SILENT in RH at beginning of words +# - H is SILENT if vowel leads and "Y" follows +# - some ".OUGH.." --> ...F exceptions added +# - "^V" transforms to "W" +# 2000-01-07 Kevin Atkinson +# Converted from header to data file. +# +# 2007-08-23 László Németh +# Add PHONE header and #PHONE keywords +# +# version 1.1 + +# Documentation: http://aspell.net/man-html/PHONEtic-Code.html + +PHONE 105 +PHONE AH(AEIOUY)-^ *H +PHONE AR(AEIOUY)-^ *R +PHONE A(HR)^ * +PHONE A^ * +PHONE AH(AEIOUY)- H +PHONE AR(AEIOUY)- R +PHONE A(HR) _ +PHONE BB- _ +PHONE B B +PHONE CQ- _ +PHONE CIA X +PHONE CH X +PHONE C(EIY)- S +PHONE CK K +PHONE COUGH^ KF +PHONE CC< C +PHONE C K +PHONE DG(EIY) K +PHONE DD- _ +PHONE D T +PHONE É< E +PHONE EH(AEIOUY)-^ *H +PHONE ER(AEIOUY)-^ *R +PHONE E(HR)^ * +PHONE ENOUGH^$ *NF +PHONE E^ * +PHONE EH(AEIOUY)- H +PHONE ER(AEIOUY)- R +PHONE E(HR) _ +PHONE FF- _ +PHONE F F +PHONE GN^ N +PHONE GN$ N +PHONE GNS$ NS +PHONE GNED$ N +PHONE GH(AEIOUY)- K +PHONE GH _ +PHONE GG9 K +PHONE G K +PHONE H H +PHONE IH(AEIOUY)-^ *H +PHONE IR(AEIOUY)-^ *R +PHONE I(HR)^ * +PHONE I^ * +PHONE ING6 N +PHONE IH(AEIOUY)- H +PHONE IR(AEIOUY)- R +PHONE I(HR) _ +PHONE J K +PHONE KN^ N +PHONE KK- _ +PHONE K K +PHONE LAUGH^ LF +PHONE LL- _ +PHONE L L +PHONE MB$ M +PHONE MM M +PHONE M M +PHONE NN- _ +PHONE N N +PHONE OH(AEIOUY)-^ *H +PHONE OR(AEIOUY)-^ *R +PHONE O(HR)^ * +PHONE O^ * +PHONE OH(AEIOUY)- H +PHONE OR(AEIOUY)- R +PHONE O(HR) _ +PHONE PH F +PHONE PN^ N +PHONE PP- _ +PHONE P P +PHONE Q K +PHONE RH^ R +PHONE ROUGH^ RF +PHONE RR- _ +PHONE R R +PHONE SCH(EOU)- SK +PHONE SC(IEY)- S +PHONE SH X +PHONE SI(AO)- X +PHONE SS- _ +PHONE S S +PHONE TI(AO)- X +PHONE TH @ +PHONE TCH-- _ +PHONE TOUGH^ TF +PHONE TT- _ +PHONE T T +PHONE UH(AEIOUY)-^ *H +PHONE UR(AEIOUY)-^ *R +PHONE U(HR)^ * +PHONE U^ * +PHONE UH(AEIOUY)- H +PHONE UR(AEIOUY)- R +PHONE U(HR) _ +PHONE V^ W +PHONE V F +PHONE WR^ R +PHONE WH^ W +PHONE W(AEIOU)- W +PHONE X^ S +PHONE X KS +PHONE Y(AEIOU)- Y +PHONE ZZ- _ +PHONE Z S + +#The rules in a different view: +# +# Exceptions: +# +# Beginning of word: "gn", "kn-", "pn-", "wr-" ----> drop first letter +# "Aebersold", "Gnagy", "Knuth", "Pniewski", "Wright" +# +# Beginning of word: "x" ----> change to "s" +# as in "Deng Xiaopeng" +# +# Beginning of word: "wh-" ----> change to "w" +# as in "Whalen" +# Beginning of word: leading vowels are transformed to "*" +# +# "[crt]ough" and "enough" are handled separately because of "F" sound +# +# +# A --> A at beginning +# _ otherwise +# +# B --> B unless at the end of word after "m", as in "dumb", "McComb" +# +# C --> X (sh) if "-cia-" or "-ch-" +# S if "-ci-", "-ce-", or "-cy-" +# SILENT if "-sci-", "-sce-", or "-scy-", or "-cq-" +# K otherwise, including in "-sch-" +# +# D --> K if in "-dge-", "-dgy-", or "-dgi-" +# T otherwise +# +# E --> A at beginnig +# _ SILENT otherwise +# +# F --> F +# +# G --> SILENT if in "-gh-" and not at end or before a vowel +# in "-gn" or "-gned" or "-gns" +# in "-dge-" etc., as in above rule +# K if before "i", or "e", or "y" if not double "gg" +# +# K otherwise (incl. "GG"!) +# +# H --> SILENT if after vowel and no vowel or "Y" follows +# or after "-ch-", "-sh-", "-ph-", "-th-", "-gh-" +# or after "rh-" at beginning +# H otherwise +# +# I --> A at beginning +# _ SILENT otherwise +# +# J --> K +# +# K --> SILENT if after "c" +# K otherwise +# +# L --> L +# +# M --> M +# +# N --> N +# +# O --> A at beginning +# _ SILENT otherwise +# +# P --> F if before "h" +# P otherwise +# +# Q --> K +# +# R --> SILENT if after vowel and no vowel or "Y" follows +# R otherwise +# +# S --> X (sh) if before "h" or in "-sio-" or "-sia-" +# SK if followed by "ch(eo)" (SCH(EO)) +# S otherwise +# +# T --> X (sh) if "-tia-" or "-tio-" +# 0 (th) if before "h" +# silent if in "-tch-" +# T otherwise +# +# U --> A at beginning +# _ SILENT otherwise +# +# V --> V if first letter of word +# F otherwise +# +# W --> SILENT if not followed by a vowel +# W if followed by a vowel +# +# X --> KS +# +# Y --> SILENT if not followed by a vowel +# Y if followed by a vowel +# +# Z --> S diff --git a/docs/hunspell/en_US.dic b/docs/hunspell/en_US.dic new file mode 100644 index 0000000..d3e620e --- /dev/null +++ b/docs/hunspell/en_US.dic @@ -0,0 +1,62155 @@ +62154 +0/nm +0s/pt +0th/pt +1/n1 +1990s +1st/p +1th/tc +2/nm +2nd/p +2th/tc +3/nm +3rd/p +3th/tc +4/nm +4th/pt +5/nm +5th/pt +6/nm +6th/pt +7/nm +7th/pt +8/nm +8th/pt +9/nm +9th/pt +A +A's +AA +AAA +AB +ABC/M +ABM/S +ABS +AC +ACLU +ACM +ACT +ACTH +AD +ADC +ADP +AF +AFAIK +AFB +AFC +AFDC +AI +AIDS +AK +AL +ALGOL +ALU +AM +AMA +ANSI/M +AOL/M +AP +APB +APO +APR +AR +ARC +ARCO/M +ARPA/M +ARPANET/M +ASAP +ASCII +ASL +ASPCA +ATM/M +ATP +ATV/S +AV +AWACS +AWOL +AZ +AZT +Aachen/M +Aaren/M +Aarhus/M +Aarika/M +Aaron/M +Ab/M +Abagael/M +Abagail/M +Abba/M +Abbe/M +Abbey/M +Abbi/M +Abbie/M +Abbot/M +Abbott/M +Abby/M +Abbye/M +Abdel/M +Abdul/M +Abe/M +Abel/M +Abelard/M +Abelson/M +Aberdeen/M +Abernathy/M +Abeu/M +Abey/M +Abidjan/M +Abie/M +Abigael/M +Abigail/M +Abigale/M +Abilene/M +Abner/M +Abo/SM! +Aborigine/SM +Abra/M +Abraham/M +Abrahan/M +Abram/SM +Abramo/M +Abramson/M +Abran/M +Abuja +Abyssinia/M +Abyssinian +Ac/M +Acadia/M +Acapulco/M +Accra/M +Acevedo/M +Achaean/M +Achebe/M +Achernar/M +Acheson/M +Achilles +Ackerman/M +Aconcagua/M +Acosta/M +Acropolis/M +Acrux/M +Acta/M +Actaeon/M +Acton/M +Acts +Ad/MN +Ada/M +Adah/M +Adair/M +Adaline/M +Adam/SM +Adamo/M +Adamson/M +Adan/M +Adana/M +Adara/M +Adda/M +Addams +Addi/M +Addia/M +Addie/M +Addison/M +Addressograph/M +Addy/M +Ade/M +Adel/M +Adela/M +Adelaida/M +Adelaide/M +Adelbert/M +Adele/M +Adelheid/M +Adelice/M +Adelina/M +Adelind/M +Adeline/M +Adella/M +Adelle/M +Aden/M +Adena/M +Adenauer/M +Adey/M +Adham/M +Adhara/M +Adi/M +Adiana/M +Adidas/M +Adina/M +Adirondack/SM +Adkins/M +Adlai/M +Adler/M +Adm/M +Admiralty/S +Ado/M +Adolf/M +Adolfo/M +Adolph/M +Adolphe/M +Adolpho/M +Adolphus/M +Adonis/SM +Adora/M +Adore/M +Adoree/M +Adorne/M +Adrea/M +Adrenalin/MS +Adria/MX +Adrian/M +Adriana/M +Adriane/M +Adrianna/M +Adrianne/M +Adriano/M +Adriatic +Adrien/M +Adriena/M +Adrienne/M +Advent/SM +Adventist/M +Advil/M +Aegean +Aelfric/M +Aeneas +Aeneid/M +Aeolus/M +Aeriel/M +Aeriela/M +Aeriell/M +Aeschylus/M +Aesculapius/M +Aesop/M +Afghan/SM +Afghani/SM +Afghanistan/M +Afr +Africa/M +African/MS +Afrikaans/M +Afrikaner/SM +Afro/MS +Afrocentric +Afrocentrism/S +Afton/M +Ag/M +Agace/M +Agamemnon/M +Agassiz/M +Agata/M +Agatha/M +Agathe/M +Aggi/M +Aggie/M +Aggy/SM +Aglaia/M +Agna/M +Agnella/M +Agnes/M +Agnese/M +Agnesse/M +Agneta/M +Agnew/M +Agni/M +Agnola/M +Agosto/M +Agra/M +Agretha/M +Agricola/M +Agrippa/M +Agrippina/M +Aguascalientes/M +Aguie/M +Aguilar/M +Aguinaldo/M +Aguirre/M +Aguistin/M +Aguste/M +Agustin/M +Ahab/M +Aharon/M +Ahmad/M +Ahmadabad +Ahmed/M +Ahriman/M +Aida/M +Aidan/M +Aigneis/M +Aiken/M +Aila/M +Ailbert/M +Aile/M +Ailee/M +Aileen/M +Ailene/M +Ailey/M +Aili/SM +Ailina/M +Ailsun/M +Ailyn/M +Aime/M +Aimee/M +Aimil/M +Aindrea/M +Ainslee/M +Ainsley/M +Ainslie/M +Ainu/M +Airbus/M +Airedale/SM +Aires +Aisha/M +Ajax/M +Ajay/M +Akbar/M +Akihito/M +Akim/M +Akita/M +Akkad/M +Akron/M +Aksel/M +Al/MRY +Ala/MS +Alabama/M +Alabaman/S +Alabamian/MS +Aladdin/M +Alain/M +Alaine/M +Alair/M +Alameda/M +Alamo/SM +Alamogordo/M +Alan/M +Alana/M +Alanah/M +Aland/M +Alane/M +Alanna/M +Alano/M +Alanson/M +Alar/M +Alard/M +Alaric/M +Alasdair/M +Alaska/M +Alaskan/S +Alastair/M +Alasteir/M +Alaster/M +Alayne/M +Alba/M +Albania/M +Albanian/SM +Albany/M +Albee/M +Alberich/M +Alberik/M +Alberio/M +Albert/M +Alberta/M +Albertan/S +Albertina/M +Albertine/M +Alberto/M +Albie/M +Albigensian +Albina/M +Albion/M +Albireo/M +Albrecht/M +Albuquerque/M +Alcatraz/M +Alcestis/M +Alcibiades/M +Alcmena/M +Alcoa/M +Alcott/M +Alcuin/M +Alcyone/M +Aldan/M +Aldebaran/M +Alden/M +Alderamin/M +Aldin/M +Aldis/M +Aldo/M +Aldon/M +Aldous/M +Aldric/M +Aldrich/M +Aldridge/M +Aldrin/M +Aldus/M +Aldwin/M +Alec/M +Alecia/M +Aleck/M +Aleda/M +Aleece/M +Aleen/M +Aleichem/M +Alejandra/M +Alejandrina/M +Alejandro/M +Alejoa/M +Aleksandr/M +Alembert/M +Alena/M +Alene/M +Aleppo/M +Aler/M +Alessandra/M +Alessandro/M +Aleta/M +Alethea/M +Aleut/SM +Aleutian/S +Alex/M +Alexa/M +Alexander/SM +Alexandr/M +Alexandra/M +Alexandre/M +Alexandria/M +Alexandrian/S +Alexandrina/M +Alexandro/MS +Alexei/M +Alexi/SM +Alexia/M +Alexina/M +Alexine/M +Alexio/M +Alf/M +Alfa/M +Alfi/M +Alfie/M +Alfons/M +Alfonse/M +Alfonso/M +Alfonzo/M +Alford/M +Alfred/M +Alfreda/M +Alfredo/M +Alfy/M +Algenib/M +Alger/M +Algeria/M +Algerian/MS +Algernon/M +Algieba/M +Algiers/M +Algol/M +Algonquian/SM +Algonquin/SM +Alhambra/M +Alhena/M +Ali/MS +Alia/M +Alic/M +Alica/M +Alice/M +Alicea/M +Alicia/M +Alick/M +Alida/M +Alidia/M +Alie/M +Alighieri/M +Alika/M +Alikee/M +Alina/M +Aline/M +Alioth/M +Alisa/M +Alisander/M +Alisha/M +Alison/M +Alissa/M +Alistair/M +Alister/M +Alisun/M +Alix/M +Aliza/M +Alkaid/M +Alla/M +Allah/M +Allahabad/M +Allan/M +Allard/M +Allayne/M +Alleen/M +Allegheny/MS +Allegra/M +Allen/M +Allendale/M +Allende/M +Allene/M +Allentown/M +Alley/M +Alleyn/M +Allhallows +Alli/MS +Allianora/M +Allie/M +Allin/M +Allina/M +Allison/M +Allissa/M +Allister/M +Allistir/M +Allix/M +Allstate/M +Allsun/M +Allx/M +Ally/MS +Allyce/M +Allyn/M +Allys +Allyson/M +Alma/M +Almach/M +Almaden/M +Almaty/M +Almeda/M +Almeria/M +Almeta/M +Almighty/M +Almira/M +Almire/M +Alnilam/M +Alnitak/M +Aloin/M +Aloise/M +Aloisia/M +Alon/M +Alonso/M +Alonzo/M +Aloysia/M +Aloysius/M +Alpert/M +Alphard/M +Alphecca/M +Alpheratz/M +Alphonse/M +Alphonso/M +Alpine +Alps +Alric/M +Alsace/M +Alsatian/MS +Alsop/M +Alston/M +Alta/M +Altai/M +Altaic/M +Altair/M +Althea/M +Altiplano/M +Alton/M +Altos/M +Aludra/M +Aluin/M +Aluino/M +Alva/M +Alvan/M +Alvarado/M +Alvarez/M +Alvaro/M +Alvera/M +Alverta/M +Alvie/M +Alvin/M +Alvina/M +Alvinia/M +Alvira/M +Alvis/M +Alvy/M +Alwin/M +Alwyn/M +Alyce/M +Alyda/M +Alyosha/M +Alys/M +Alysa/M +Alyse/M +Alysia/M +Alyson/M +Alyss +Alyssa/M +Alzheimer/M +Am/MR +Amabel/M +Amabelle/M +Amadeus/M +Amado/M +Amalea/M +Amalee/M +Amaleta/M +Amalia/M +Amalie/M +Amalita/M +Amalle/M +Amanda/M +Amandi/M +Amandie/M +Amandy/M +Amara/M +Amargo/M +Amarillo/M +Amata/M +Amati/M +Amazon/SM +Amazonian +Amber/YM +Amberly/M +Amble/M +Ambros/M +Ambrose/M +Ambrosi/M +Ambrosio/M +Ambrosius/M +Ambur/M +Amby/M +Amdahl/M +Ame/SM +Amelia/M +Amelie/M +Amelina/M +Ameline/M +Amelita/M +Amenhotep/M +Amer/M +Amerada/M +Amerasian/S +America/SM +American/MS +Americana/M +Americanism/SM +Americanization/SM +Americanize/SDG +Amerigo/M +Amerind/MS +Amerindian/MS +Amery/M +Ameslan/M +Amharic/M +Amherst/M +Ami/M +Amie/M +Amiga/M +Amii/M +Amil/M +Amish +Amitie/M +Amity/M +Ammamaria/M +Amman/M +Ammerman/M +Amoco/M +Amontillado/M +Amory/M +Amos +Amparo/M +Ampere/M +Ampex/M +Amritsar/M +Amsterdam/M +Amtrak/M +Amundsen/M +Amur/M +Amway/M +Amy/M +Amye/M +Ana/M +Anabal/M +Anabaptist/SM +Anabel/M +Anabella/M +Anabelle/M +Anacin/M +Anacreon/M +Anaheim/M +Analects/M +Analiese/M +Analise/M +Anallese/M +Anallise/M +Ananias/M +Anastasia/M +Anastasie/M +Anastassia/M +Anatol/M +Anatola/M +Anatole/M +Anatolia/M +Anatolian +Anatollo/M +Anaxagoras/M +Ancell/M +Anchorage/M +Andalusia/M +Andalusian +Andaman +Andean/M +Andee/M +Andeee/M +Anderea/M +Anders/N +Andersen/M +Anderson/M +Andes +Andi/M +Andie/M +Andonis/M +Andorra/M +Andover/M +Andra/SM +Andre/SM +Andrea/MS +Andreana/M +Andree/M +Andrei/M +Andrej/M +Andrew/MS +Andrey/M +Andria/M +Andriana/M +Andriette/M +Andris +Andromache/M +Andromeda/M +Andropov/M +Andros/M +Andrus/M +Andy/M +Anestassia/M +Anet/M +Anett/M +Anetta/M +Anette/M +Angara/M +Ange/M +Angel/M +Angela/M +Angele/SM +Angeleno/SM +Angeli/M +Angelia/M +Angelica/M +Angelico/M +Angelika/M +Angelina/M +Angeline/M +Angelique/M +Angelita/M +Angelle/M +Angelo/M +Angelou/M +Angevin/M +Angie/M +Angil/M +Angkor/M +Angles +Anglia/M +Anglican/MS +Anglicanism/MS +Anglicism/SM +Anglicization/MS +Anglicize/SDG +Anglo/MS +Anglophile/SM +Anglophilia/M +Anglophobe/MS +Anglophobia/M +Angola/M +Angolan/S +Angora/MS +Anguilla/M +Angus/M +Angy/M +Anheuser/M +Ania/M +Aniakchak/M +Anibal/M +Anica/M +Anissa/M +Anita/M +Anitra/M +Anjanette/M +Anjela/M +Ankara/M +Ann/M +Anna/M +Annabal/M +Annabel/M +Annabela/M +Annabell/M +Annabella/M +Annabelle/M +Annadiana/M +Annadiane/M +Annalee/M +Annaliese/M +Annalise/M +Annamaria/M +Annamarie/M +Annapolis/M +Annapurna/M +Anne/M +Annecorinne/M +Anneliese/M +Annelise/M +Annemarie/M +Annetta/M +Annette/M +Anni/MS +Annice/M +Annie/M +Annissa/M +Annmaria/M +Annmarie/M +Annnora/M +Annora/M +Annunciation/S +Anny/M +Anouilh/M +Ansel/M +Ansell/M +Anselm/M +Anselma/M +Anselmo/M +Anshan/M +Ansley/M +Anson/M +Anstice/M +Antaeus/M +Antananarivo/M +Antarctic/M +Antarctica/M +Antares +Anthe/M +Anthea/M +Anthia/M +Anthiathia/M +Anthony/M +Antichrist/MS +Antietam/M +Antigone/M +Antigua/M +Antillean +Antilles +Antin/M +Antioch/M +Antipas/M +Antipodes +Antofagasta/M +Antoine/M +Antoinette/M +Anton/MS +Antone/M +Antonella/M +Antonetta/M +Antoni/M +Antonia/M +Antonie/M +Antonietta/M +Antonin/M +Antonina/M +Antonino/M +Antoninus/M +Antonio/M +Antonius/M +Antonovics/M +Antony/M +Antwan/M +Antwerp/M +Anubis/M +Any/M +Anya/M +Apache/MS +Apalachicola/M +Apennines +Aphrodite/M +Apia/M +Apocalypse/M +Apocrypha/M +Apollinaire/M +Apollo/SM +Apollonian +Appalachia/M +Appalachian/MS +Appaloosa/MS +Appia/M +Appian/M +Apple/M +Appleseed/M +Appleton/M +Appolonia/M +Appomattox/M +Apr/M +April/MS +Aprilette/M +Apuleius/M +Aquarius/MS +Aquila/M +Aquinas/M +Aquino/M +Aquitaine/M +Ar/MY +Ara/M +Arab/MS +Arabel/M +Arabela/M +Arabele/M +Arabella/M +Arabelle/M +Arabia/M +Arabian/MS +Arabic/M +Arabist/MS +Araby/M +Araceli/M +Arafat/M +Araguaya/M +Aral/M +Araldo/M +Aramaic/M +Aramco/M +Arapaho/MS +Arapahoe's +Arapahoes +Ararat/M +Araucanian/M +Arawak/M +Arawakan/M +Arcadia/M +Arcadian +Arch/MR +Archaimbaud/M +Archambault/M +Archean +Archer/M +Archibald/M +Archibaldo/M +Archibold/M +Archie/M +Archimedes/M +Archy/M +Arctic/M +Arcturus/M +Arda/MH +Ardabil +Ardath/M +Ardeen/M +Ardelia/M +Ardelis/M +Ardella/M +Ardelle/M +Arden/M +Ardene/M +Ardenia/M +Ardine/M +Ardis/M +Ardisj/M +Ardith/M +Ardra/M +Ardyce/M +Ardys +Ardyth/M +Arel/M +Arequipa/M +Ares +Aretha/M +Argentina/M +Argentine/SM +Argentinean/S +Argentinian/S +Argo/SM +Argonaut/MS +Argonne/M +Argus/M +Ari/M +Ariadne/M +Ariana/M +Arianism/M +Arianist/SM +Aridatha/M +Arie/SM +Ariel/M +Ariela/M +Ariella/M +Arielle/M +Aries/S +Arin/M +Ario/M +Ariosto/M +Aristarchus/M +Aristides +Aristophanes/M +Aristotelean +Aristotelian/M +Aristotle/M +Arius/M +Ariz/M +Arizona/M +Arizonan/S +Arizonian/S +Arjuna/M +Ark/M +Arkansan/MS +Arkansas/M +Arkhangelsk/M +Arkwright/M +Arlan/M +Arlana/M +Arlee/M +Arleen/M +Arlen/M +Arlena/M +Arlene/M +Arleta/M +Arlette/M +Arley/M +Arleyne/M +Arlie/M +Arliene/M +Arlin/M +Arlina/M +Arlinda/M +Arline/M +Arlington/M +Arluene/M +Arly/M +Arlyn/M +Arlyne/M +Armada/M +Armageddon/SM +Armagnac/M +Arman/M +Armand/M +Armando/M +Armata/M +Armco/M +Armenia/M +Armenian/MS +Armin/M +Arminius/M +Armonk/M +Armour/M +Armstrong/M +Arnaldo/M +Arne/M +Arneb/M +Arney/M +Arnhem/M +Arni/M +Arnie/M +Arno/M +Arnold/M +Arnoldo/M +Arnuad/M +Arnulfo/M +Arny/M +Aron/M +Arpanet/M +Arragon/M +Arrhenius/M +Arri/M +Arron/M +Art/M +Artair/M +Artaxerxes/M +Arte/M +Artemas +Artemis/M +Artemus/M +Arther/M +Arthur/M +Arthurian +Artie/M +Artur/M +Arturo/M +Artus/M +Arty/M +Aruba/M +Arv/M +Arvie/M +Arvin/M +Arvy/M +Aryan/MS +Aryn/M +As +Asa/M +Asama/M +Ascella/M +Ascension/M +Ase/M +Asgard/M +Ash/MRY +Ashanti/M +Ashbey/M +Ashby/M +Ashely/M +Asher/M +Asheville/M +Ashia/M +Ashien/M +Ashil/M +Ashkenazim +Ashkhabad/M +Ashla/M +Ashlan/M +Ashland/M +Ashlee/M +Ashleigh/M +Ashlen/M +Ashley/M +Ashli/M +Ashlie/M +Ashlin/M +Ashly/M +Ashmolean/M +Ashton/M +Ashurbanipal/M +Asia/M +Asian/MS +Asiatic/SM +Asilomar/M +Asimov +Asmara/M +Asoka/M +Aspell/M +Aspen/M +Aspidiske/M +Asquith/M +Assad/M +Assam/M +Assamese/M +Assembly/MS +Assisi/M +Assyria/M +Assyrian/SM +Assyriology/M +Astaire/SM +Astarte/M +Aston/M +Astor/M +Astoria/M +Astra/M +Astrakhan/M +Astrid/M +Astrix/M +AstroTurf/S +Astroturf/M +Asturias/M +Asunción/M +Aswan/M +At/M +Atacama/M +Atahualpa/M +Atalanta/M +Atari/M +Atatürk/M +Athabasca/M +Athabascan's +Athabaska's +Athabaskan/MS +Athena/M +Athene/M +Athenian/SM +Athens/M +Atkins/M +Atkinson/M +Atlanta/M +Atlante/MS +Atlantic/M +Atlantis/M +Atlas/SM +Atman +Atreus/M +Atria/M +Atropos/M +Ats +Attic +Attica/M +Attila/M +Attlee/M +Attn +Attucks +Atwood/M +Au/M +Aube/M +Auberge/M +Auberon/M +Aubert/M +Auberta/M +Aubine/M +Aubree/M +Aubrette/M +Aubrey/M +Aubrie/M +Aubry/M +Auckland/M +Auden/M +Audi/M +Audie/M +Audra/M +Audre/M +Audrey/M +Audrie/M +Audry/M +Audrye/M +Audubon/M +Audy/M +Auerbach/M +Aug/M +Augean +Augie/M +August/SM +Augusta/M +Augustan/S +Auguste/M +Augustin/M +Augustina/M +Augustine/M +Augustinian/S +Augusto/M +Augustus/M +Augy/M +Aundrea/M +Aura/M +Aurea/M +Aurel/M +Aurelea/M +Aurelia/M +Aurelie/M +Aurelio/M +Aurelius/M +Aureomycin/M +Auria/M +Aurie/M +Auriga/M +Aurilia/M +Aurlie/M +Auroora/M +Aurora/M +Aurore/M +Aurthur/M +Auschwitz/M +Aussie/MS +Austen/M +Austin/SM +Austina/M +Austine/M +Australasia/M +Australasian/S +Australia/M +Australian/MS +Australis/M +Australoid +Australopithecus/M +Austria/M +Austrian/SM +Austronesian +Autumn/M +Ava/M +Avalon/M +Ave/MS +Aveline/M +Aventine/M +Aventino/M +Averell/M +Averil/M +Averill/M +Avernus/M +Averroes/M +Avery/M +Averyl/M +Avesta/M +Avicenna/M +Avictor/M +Avie/M +Avigdor/M +Avignon/M +Avila/M +Avior/M +Avis +Aviv/M +Aviva/M +Avivah/M +Avogadro/M +Avon/M +Avram/M +Avril/M +Avrit/M +Avrom/M +Ax/M +Axe/M +Axel/M +Ayala/M +Ayers +Aylmar/M +Aylmer/M +Aymara/M +Aymer/M +Ayn/M +Azania/M +Azazel/M +Azerbaijan/M +Azores +Azov/M +Aztec/MS +Aztecan +B's +B/GT +BA +BASIC +BB +BBB +BBC +BBQ +BBS +BC +BCD +BIOS +BITNET/M +BLT +BM +BMW/M +BO +BR +BS +BSA +BSD +BTU +BTW +BYOB +Ba/M +Baal/SM +Bab/SM +Babar's +Babara/M +Babb/M +Babbage/M +Babbette/M +Babbie/M +Babbitt/M +Babcock/M +Babel/MS +Babette/M +Babita/M +Babka/M +Babylon/MS +Babylonia/M +Babylonian/SM +Bacall/M +Bacardi/M +Bacchanalia/M +Bacchic +Bacchus/M +Bach/M +Backus/M +Bacon/M +Bactria/M +Baden/M +Badlands/M +Baedeker/SM +Baez/M +Baffin/M +Baghdad/M +Bagrodia/MS +Baguio/M +Baha'i +Baha'ullah +Bahama/MS +Bahamanian/S +Bahamian/MS +Bahia/M +Bahrain/M +Baikal/M +Bail/M +Bailey/SM +Bailie/M +Baillie/M +Baily/M +Baird/M +Baja/M +Bakelite/M +Baker/M +Bakersfield/M +Baku/M +Bakunin/M +Balanchine/M +Balboa/M +Bald/MR +Balder/M +Balduin/M +Baldwin/M +Bale/M +Balearic/M +Balfour/M +Bali/M +Balinese +Balkan/SM +Balkhash/M +Ball/M +Ballard/SM +Balthazar/M +Baltic/M +Baltimore/M +Baluchistan/M +Balzac/M +Bamako/M +Bamberger/M +Bambi/M +Bambie/M +Bamby/M +Ban/M +Banach/M +Bancroft/M +Bandung/M +Bangalore/M +Bangkok/M +Bangladesh/M +Bangladeshi/S +Bangor/M +Bangui/M +Banjarmasin/M +Banjul/M +Bank/MS +Banky/M +Banneker/M +Bannister/M +Banting/M +Bantu/SM +Baotou/M +Baptist/MS +Baptiste/M +Bar/MH +Barabbas/M +Barb/RM +Barbabas/M +Barbabra/M +Barbadian/S +Barbados/M +Barbara/M +Barbaraanne/M +Barbarella/M +Barbarossa/M +Barbary/M +Barbe/M +Barbee/M +Barber/M +Barbette/M +Barbey/M +Barbi/M +Barbie/M +Barbour/M +Barbra/M +Barbuda/M +Barby/M +Barcelona/M +Barclay/M +Bard/M +Barde/M +Bardeen/M +Barents/M +Bari/M +Barker/M +Barkley/M +Barlow/M +Barn/M +Barnabas +Barnabe/M +Barnaby/M +Barnard/M +Barnaul/M +Barnebas/M +Barnes +Barnett/M +Barney/M +Barnhard/M +Barnie/M +Barnum/M +Barny/M +Baroda/M +Baron/M +Barquisimeto/M +Barr/M +Barranquilla/M +Barrera/M +Barret/M +Barrett/M +Barri/SM +Barrie/M +Barron/M +Barry/M +Barrymore/MS +Barstow/M +Bart/M +Bartel/M +Barth/M +Barthel/M +Bartholdi/M +Bartholemy/M +Bartholomeo/M +Bartholomeus/M +Bartholomew/M +Bartie/M +Bartlet/M +Bartlett/M +Bartolemo/M +Bartolomeo/M +Barton/M +Bartram/M +Barty/M +Bartók/M +Bary/M +Baryram/M +Baryshnikov/M +Bascom/M +Base/M +Basel/M +Basho/M +Basia/M +Basie/M +Basil/M +Basile/M +Basilio/M +Basilius/M +Basque/SM +Basra/M +Bass/M +Basseterre/M +Bassett/M +Bastian/M +Bastien/M +Bastille/M +Basutoland/M +Bat/M +Bataan/M +Batavia/M +Bates +Batholomew/M +Bathsheba/M +Batista/M +Batman/M +Batsheva/M +Battle/M +Batu/M +Baudelaire/M +Baudoin/M +Baudouin/M +Bauer/M +Bauhaus/M +Bausch/M +Bavaria/M +Bavarian/S +Bax/M +Baxie/M +Baxter/M +Baxy/M +Bay/MR +Bayamon +Bayard/M +Bayda/M +Bayer/M +Bayes +Bayesian +Baylor/M +Bayonne/M +Bayreuth/M +Be/MH +Bea/M +Beach/M +Beadle/M +Beale/M +Bealle/M +Bean/M +Bear/M +Beard/M +Beardmore/M +Beardsley/M +Bearnaise/M +Bearnard/M +Beasley/M +Beatlemania/M +Beatles/M +Beatrice/M +Beatrisa/M +Beatrix/M +Beatriz/M +Beau/M +Beauchamps +Beaufort/M +Beaujolais/M +Beaumarchais/M +Beaumont/M +Beauregard/M +Beauvoir/M +Beaverton/M +Bebe/M +Becca/M +Bechtel/M +Beck/RM +Becka/M +Becker/M +Becket/M +Beckett/M +Becki/M +Beckie/M +Becky/M +Becquerel/M +Bede/M +Bedford/M +Bedouin/SM +Bee/M +Beebe/M +Beecher/M +Beelzebub/M +Beerbohm/M +Beethoven/M +Beeton/M +Begin/M +Behan/M +Behring/M +Beiderbecke/M +Beijing +Beilul/M +Beirut/M +Beitris/M +Bekesy/M +Bekki/M +Bel/M +Bela/M +Belarus +Belau/M +Belem/M +Belfast/M +Belg/M +Belgian/MS +Belgium/M +Belgrade/M +Belia/M +Belicia/M +Belinda/M +Belita/M +Belize/M +Bell/M +Bella/M +Bellamy/M +Bellanca/M +Bellatrix/M +Belle/M +Belleville/M +Bellina/M +Bellini/M +Bellovin/M +Bellow/M +Bellwood/M +Belmont/M +Belmopan/M +Beloit/M +Belorussia's +Belorussian/S +Belshazzar/M +Belton/M +Beltran/M +Beltsville/M +Belushi/M +Belva/M +Belvia/M +Ben/M +Benacerraf/M +Benares's +Bender/M +Bendick/M +Bendicty/M +Bendite/M +Bendix/M +Benedetta/M +Benedetto/M +Benedick/M +Benedict/M +Benedicta/M +Benedictine/MS +Benedicto/M +Benedikt/M +Benedikta/M +Benelux/M +Benet/M +Benetta/M +Benetton/M +Bengal/SM +Bengali/M +Benghazi/M +Bengt/M +Beniamino/M +Benin/M +Beninese +Benita/M +Benito/M +Benjamen/M +Benjamin/M +Benji/M +Benjie/M +Benjy/M +Benn/M +Bennett/M +Benni/M +Bennie/M +Bennington/M +Benny/M +Benoit/M +Benoite/M +Benson/M +Bent/M +Bentham/M +Bentlee/M +Bentley/MS +Benton/M +Benyamin/M +Benz/M +Benzedrine/M +Beograd's +Beowulf/M +Ber/MG +Berber/MS +Berenice/M +Beret/M +Berg/NRM +Bergen/M +Berger/M +Bergerac/M +Berget/M +Berglund/M +Bergman/M +Bergson/M +Bergsten/M +Bergstrom/M +Bering/RM +Beringer/M +Berk/YM +Berke/M +Berkeley/M +Berkie/M +Berkley/M +Berkly/M +Berkowitz/M +Berkshire/SM +Berky/M +Berle/M +Berlin/SZRM +Berliner/M +Berlioz/M +Berlitz/M +Berman/M +Bermuda/MS +Bermudan/S +Bermudian/S +Bern/M +Berna/M +Bernadene/M +Bernadette/M +Bernadina/M +Bernadine/M +Bernard/M +Bernardina/M +Bernardine/M +Bernardino/M +Bernardo/M +Bernarr/M +Bernays/M +Bernbach/M +Berne's +Bernelle/M +Bernese +Bernete/M +Bernetta/M +Bernette/M +Bernhard/M +Bernhardt/M +Berni/M +Bernice/M +Bernie/M +Berniece/M +Bernini/M +Bernita/M +Bernoulli/M +Bernstein/M +Berny/M +Berra/M +Berri/M +Berrie/M +Berry/M +Bert/M +Berta/M +Berte/M +Bertha/M +Berthe/M +Berti/M +Bertie/M +Bertillon/M +Bertina/M +Bertine/M +Berton/M +Bertram/M +Bertrand/M +Bertrando/M +Berty/M +Beryl/M +Beryle/M +Berzelius/M +Bess +Bessel/M +Bessemer/M +Bessie/M +Bessy/M +Best/M +Betelgeuse/M +Beth/M +Bethanne/M +Bethany/M +Bethe/M +Bethena/M +Bethesda/M +Bethina/M +Bethlehem/M +Bethune +Betsey/M +Betsy/M +Betta/M +Bette/M +Betteann/M +Betteanne/M +Betti/M +Bettie/M +Bettina/M +Bettine/M +Betty/SM +Bettye/M +Beulah/M +Bev's +Bevan/M +Beverie/M +Beverlee/M +Beverley/M +Beverlie/M +Beverly/M +Bevin/M +Bevon/M +Bevvy/M +Bhopal/M +Bhutan/M +Bhutanese +Bhutto/M +Bi/M +Bialystok/M +Bianca/M +Bianco/M +Bianka/M +Bib/M +Bibbie/M +Bibby/M +Bibbye/M +Bibi/M +Bible/MS +Biddie/M +Biddle/M +Biddy/M +Bidget/M +Bienville/M +Bierce/M +Bigelow/M +Bigfoot +Biko/M +Bil/MY +Bilbao/M +Bilbo/M +Bili/M +Bill/JM +Billi/M +Billie/M +Billings/M +Billy/M +Billye/M +Bimini/M +Bing/M +Bingham/M +Binghamton/M +Bini/M +Bink/M +Binky/M +Binni/M +Binnie/M +Binny/M +Bioko/M +Birch/M +Bird/M +Birdie/M +Birdseye/M +Birgit/M +Birgitta/M +Birk/M +Birkenstock/M +Birmingham/M +Biro/M +Biron/M +Biscay/M +Biscayne/M +Bishkek +Bishop/M +Bismarck/M +Bismark/M +Bissau/M +Bizet/M +Bjorn/M +Bk/M +Black's +Blackburn/M +Blackfeet +Blackfoot/M +Blackman/M +Blackmer/M +Blackpool/M +Blackstone/M +Blackwell/MS +Blaine/M +Blair/M +Blaire/M +Blake/M +Blakelee/M +Blakeley/M +Blakey/M +Blanca/M +Blanch/M +Blancha/M +Blanchard/M +Blanche/M +Blane/M +Blankenship/M +Blanton/M +Blantyre/M +Blatz/M +Blavatsky/M +Blayne/M +Bleeker/M +Blenheim/M +Blevins/M +Bligh/M +Blinni/M +Blinnie/M +Blinny/M +Bliss/M +Blisse/M +Blithe/M +Bloch/M +Bloemfontein/M +Blomberg/M +Blomquist/M +Blondell/M +Blondelle/M +Blondie/M +Blondy/M +Bloom/MR +Bloomer/M +Bloomfield/M +Bloomington/M +Blucher/M +Bluebeard/M +Blum/M +Blumenthal/M +Blvd +Blythe/M +Bo/MRZ +Bob/M +Bobbe/M +Bobbee/M +Bobbette/M +Bobbi/M +Bobbie/M +Bobbitt/M +Bobbsey/M +Bobby/M +Bobbye/M +Bobette/M +Bobina/M +Bobine/M +Bobinette/M +Bobrow/M +Boca/M +Boccaccio/M +Bodenheim/M +Bodhidharma/M +Bodhisattva/M +Boeing/M +Boeotia/M +Boeotian +Boer/M +Bogart/M +Bogartian/M +Bogey/M +Bogotá/M +Boheme/M +Bohemia/SM +Bohemian/SM +Bohr/M +Boigie/M +Bois/M +Boise/M +Boleyn/M +Bolivar/M +Bolivia/M +Bolivian/S +Bologna/M +Bolshevik/MS +Bolshevism/MS +Bolshevist/MS +Bolshevistic/M +Bolshoi/M +Bolton/M +Boltzmann/M +Bombay/M +Bonaparte/M +Bonaventure/M +Bond/M +Bondie/M +Bondon/M +Bondy/M +Bone/M +Bonham/M +Boniface/M +Bonita/M +Bonn/RM +Bonnee/M +Bonner/M +Bonneville/M +Bonni/M +Bonnibelle/M +Bonnie/M +Bonny/M +Bontempo/M +Booker/M +Boole/M +Boolean +Boone/M +Boonie/M +Boony/M +Boot/M +Boote/M +Booth/M +Boothe/M +Bootle/M +Bord/MN +Bordeaux/M +Borden/M +Bordie/M +Bordon/M +Bordy/M +Borealis/M +Boreas/M +Borg/M +Borges +Borgia/M +Boris +Bork/M +Born/M +Borneo/M +Borodin/M +Borroughs/M +Boru/M +Bosch/M +Bose/M +Bosnia/M +Bosnian/S +Bosporus/M +Bostitch/M +Boston/MS +Bostonian/SM +Boswell/MS +Botswana/M +Botticelli/M +Boucher/M +Boulder/M +Bourbaki/M +Bourbon/SM +Bourke/M +Bourne/M +Bournemouth/M +Bouvier +Bovary/M +Bowditch/M +Bowell/M +Bowen/M +Bowers +Bowery/M +Bowes +Bowie/M +Bowman/M +Boy/MR +Boyce/M +Boycey/M +Boycie/M +Boyd/M +Boyer/M +Boyle/M +Boötes +Br/TMN +Brad/MYN +Bradan/M +Bradbury/M +Bradburys +Braddock/M +Brade/M +Braden/M +Bradford/M +Bradley/M +Bradly/M +Bradney/M +Bradshaw/M +Bradstreet/M +Brady/M +Bragg/M +Brahe/M +Brahma/MS +Brahman/SM +Brahmanism/MS +Brahmaputra/M +Brahmin's +Brahms +Braille/GDSM +Brain/M +Brainard/SM +Bram/M +Brampton/M +Bran/M +Brana/M +Branch/M +Branchville/M +Brand/MRN +Brandais/M +Brande/M +Brandea/M +Brandeis/M +Brandel/M +Branden/M +Brandenburg/M +Brander/M +Brandi/M +Brandice/M +Brandie/M +Brandise/M +Brando/M +Brandon/M +Brandt/M +Brandtr/M +Brandy/M +Brandyn/M +Braniff/M +Brannon/M +Brant/M +Brantley/M +Braque/M +Brasilia +Bratislava/M +Brattain/M +Braun/M +Bray/M +Brazil/M +Brazilian/MS +Brazos/M +Brazzaville/M +Breanne/M +Brear/M +Breathalyzer/SM +Brecht/M +Breckenridge/M +Bree/M +Breena/M +Bremen/M +Bren/M +Brena/M +Brenda/M +Brendan/M +Brenden/M +Brendin/M +Brendis/M +Brendon/M +Brenn/RNM +Brenna/M +Brennan/M +Brennen/M +Brenner/M +Brent/M +Brenton/M +Bresenham/M +Brest/M +Bret/M +Breton +Brett/M +Brew/RM +Brewer/M +Brewster/M +Brezhnev/M +Bria/M +Brian/M +Briana/M +Brianna/M +Brianne/M +Briano/M +Briant/M +Brice/M +Bridalveil/M +Bride/M +Bridewell/M +Bridgeport/M +Bridger/M +Bridges +Bridget/M +Bridgetown/M +Bridgett/M +Bridgette/M +Bridgewater/M +Bridgman/M +Bridie/M +Brie/RSM +Brien/M +Brier/M +Brietta/M +Brig/M +Brigadoon +Brigg/MS +Brigham/M +Bright/M +Brighton/M +Brigid/M +Brigida/M +Brigit/M +Brigitta/M +Brigitte/M +Brillo +Brillouin/M +Brina/M +Brindisi/M +Briney/M +Brinkley/M +Brinn/M +Brinna/M +Briny/M +Brion/M +Brisbane/M +Bristol/M +Brit/MS +Brita/M +Britain/M +Britannia/M +Britannic +Britannica/M +Briticism/MS +British/RYZ +Britisher/M +Britishly/M +Britney/M +Britni/M +Briton/MS +Britt/MN +Britta/M +Brittan/M +Brittaney/M +Brittani/M +Brittany/MS +Britte/M +Britten/M +Britteny/M +Brittne/M +Brittney/M +Brittni/M +Brnaba/M +Brnaby/M +Brno/M +Broadway/SM +Brobdingnag/M +Brobdingnagian +Brock/M +Brockie/M +Brocky/M +Brod/M +Broddie/M +Broddy/M +Broderic/M +Broderick/M +Brodie/M +Brody/M +Broglie/M +Brok/M +Bron/M +Bronnie/M +Bronny/M +Bronson/M +Bronte +Bronx/M +Brook/SM +Brookdale/M +Brooke/M +Brookfield/M +Brookhaven/M +Brooklyn/M +Brookmont/M +Bros +Brose/M +Brown/MG +Browne/M +Brownell/M +Brownian/M +Brownie/MS +Browning/M +Brownsville/M +Brubeck/M +Bruce/M +Brucie/M +Bruckner/M +Bruegel/M +Brueghel's +Bruis/M +Brumidi/M +Brummel/M +Brunei/M +Brunelleschi/M +Brunhilda/M +Brunhilde/M +Bruno/M +Brunswick/M +Brussels +Brutus/M +Bruxelles/M +Bryan/M +Bryana/M +Bryant/M +Bryanty/M +Bryce/M +Bryn/M +Bryna/M +Brynn/RM +Brynna/M +Brynne/M +Brynner/M +Bryon/M +Brzezinski/M +Btu +Buber/M +Buchanan/M +Bucharest/M +Buchenwald/M +Buchwald/M +Buck/M +Buckie/M +Buckingham/M +Buckley/M +Buckner/M +Bucky/M +Bud/M +Budapest/M +Budd/M +Buddha/MS +Buddhism/SM +Buddhist/SM +Buddie/M +Buddy/M +Budweiser/MS +Buehring/M +Buena/M +Buffalo/M +Buffy/M +Buford/M +Bugatti/M +Buick/M +Buiron/M +Bujumbura/M +Bukhara/M +Bukharin/M +Bulawayo/M +Bulba/M +Bulfinch/M +Bulganin/M +Bulgaria/M +Bulgarian/S +Bullock/M +Bullwinkle/M +Bultmann/M +Bumbry/M +Bumppo/M +Bunche/M +Bundestag/M +Bundy/M +Bunin/M +Bunker/M +Bunni/M +Bunnie/M +Bunny/M +Bunsen/SM +Bunyan/M +Burbank/M +Burch/M +Burg/RM +Burger/M +Burgess/M +Burgoyne/M +Burgundian/S +Burgundy/MS +Burk/SM +Burke/M +Burl/M +Burlie/M +Burlingame/M +Burlington/M +Burma/M +Burmese +Burnaby/M +Burnard/M +Burne/MS +Burnett/M +Burns +Burnside/MS +Burr/M +Burris/M +Burroughs/M +Bursa/M +Burt/M +Burtie/M +Burton/M +Burty/M +Burundi/M +Burundian/S +Busch/M +Bush/M +Bushido/M +Bushnell/M +Butch/M +Butler/M +Butterfield/M +Buxtehude/M +Buñuel/M +Byelorussia's +Byers/M +Byram/M +Byran/M +Byrann/M +Byrd/M +Byrle/M +Byrne/M +Byrom/M +Byron/M +Byronic +Byronism/M +Byzantine/S +Byzantium/M +C +C's +CA +CACM +CAD +CAI +CALCOMP/M +CAM +CAP +CARE +CATV +CB +CBC +CBS +CCTV +CCU +CD +CDC/M +CDT +CENTREX/M +CEO +CERN/M +CF +CFC +CFO +CIA +CID +CMOS +CNN +CNS +CO +COBOL +COD +COL +COLA +CPA +CPI +CPO +CPR +CPU/SM +CRT/S +CST +CT +CV +CZ +Ca/M +Cabernet/M +Cabot/M +Cabrera/M +Cabrini/M +Cacilia/M +Cacilie/M +Cad/M +Caddric/M +Cadette/S +Cadillac/MS +Cadiz/M +Caedmon/M +Caesar/MS +Cage/M +Cagney/M +Cahokia/M +Cahra/M +Caiaphas/M +Cain/MS +Caine/M +Cairistiona/M +Cairo/M +Caitlin/M +Caitrin/M +Cajun/MS +Cal/MY +CalComp/M +Calais/M +Calcomp/M +Calcutta/M +Calder/M +Calderon/M +Caldwell/M +Cale/M +Caleb/M +Caledonia/M +Calgary/M +Calhoun/M +Cali/M +Caliban/M +Calida/M +Calif/M +California/M +Californian/MS +Caligula/M +Calla/MS +Callaghan/M +Callahan/M +Callao/M +Callean/M +Calley/M +Calli/M +Callida/M +Callie/M +Calliope/M +Callisto/M +Cally/M +Caloocan/M +Caltech/M +Calumet/M +Calv/M +Calvary/M +Calvert/M +Calvin/M +Calvinism/MS +Calvinist/MS +Calvinistic +Calypso/M +Cam/M +Camacho/M +Camala/M +Cambodia/M +Cambodian/S +Cambrian/S +Cambridge/M +Camden/M +Camel/M +Camella/M +Camellia/M +Camelopardalis/M +Camelot/M +Camembert/MS +Cameron/M +Cameroon/SM +Cameroonian/S +Camey/M +Cami/M +Camila/M +Camile/M +Camilla/M +Camille/M +Camino/M +Cammi/M +Cammie/M +Cammy/M +Camoens/M +Campbell/M +Campbellsport/M +Campinas/M +Campos +Camry/M +Camus/M +Can/M +Canaan/M +Canaanite/SM +Canad/M +Canada/M +Canadian/S +Canadianism/SM +Canaletto/M +Canaries +Canaveral/M +Canberra/M +Cancer/MS +Cancun/M +Candace/M +Candi/SM +Candice/M +Candida/M +Candide/M +Candie/M +Candlewick/M +Candra/M +Candy/M +Canis/M +Cannes +Cannon/M +Canoga/M +Canonical +Canonical's +Canopus/M +Cantabrigian +Canterbury/M +Canton/M +Cantonese/M +Cantor/M +Cantrell/M +Cantu/M +Canute/M +Capek/M +Capella/M +Capet/M +Capetown/M +Caph/M +Capistrano/M +Capitan/M +Capitol/MS +Capitoline/M +Capone/M +Capote/M +Cappy/M +Capra/M +Capri/M +Caprice/M +Capricorn/MS +Capt/M +Capulet/M +Caputo/M +Car/MNY +Cara/M +Caracalla/M +Caracas/M +Caralie/M +Caravaggio/M +Carboloy/M +Carbondale/M +Carbone/MS +Carboniferous +Carborundum/MS +Carce/M +Cardenas/M +Cardiff/M +Cardin/M +Cardiod/M +Care/M +Caren/M +Carena/M +Caresa/M +Caressa/M +Caresse/M +Carey/M +Cari/M +Caria/M +Carib/M +Caribbean/S +Carie/M +Caril/M +Carilyn/M +Carin/M +Carina/M +Carine/M +Cariotta/M +Carissa/M +Carita/M +Caritta/M +Carl/MNG +Carla/M +Carlee/M +Carleen/M +Carlen/M +Carlene/M +Carleton/M +Carletonian/M +Carley/M +Carlie/M +Carlin/M +Carlina/M +Carline/M +Carling/M +Carlita/M +Carlo/SM +Carlota/M +Carlotta/M +Carlsbad/M +Carlson/M +Carlton/M +Carly/M +Carlye/M +Carlyle/M +Carlyn/M +Carlynn/M +Carlynne/M +Carma/M +Carmel/M +Carmela/M +Carmelia/M +Carmelina/M +Carmelita/M +Carmella/M +Carmelle/M +Carmelo/M +Carmen/M +Carmencita/M +Carmichael/M +Carmina/M +Carmine/M +Carmita/M +Carmon/M +Carnap/M +Carnegie/M +Carney/M +Carnot/M +Carny/M +Caro/M +Carol/M +Carola/M +Carolan/M +Carolann/M +Carole/M +Carolee/M +Carolin/M +Carolina/MS +Caroline/M +Carolingian +Carolinian/S +Caroljean/M +Carolus/M +Carolyn/M +Carolyne/M +Carolynn/M +Caron/M +Carpathian/MS +Carpenter/M +Carr/M +Carree/M +Carri/M +Carrie/M +Carrier/M +Carrillo/M +Carrissa/M +Carrol/M +Carroll/M +Carry/MR +Carson/M +Cart/MR +Carter/M +Cartesian +Carthage/M +Carthaginian/S +Cartier/M +Cartwright/M +Carty/RM +Caruso/M +Carver/M +Cary/M +Caryl/M +Caryn/M +Casablanca/M +Casals/M +Casandra/M +Casanova/SM +Casar/M +Cascades/M +Case/M +Casey/M +Cash/M +Casi/M +Casie/M +Caspar/M +Casper/M +Caspian +Cass +Cassandra/SM +Cassandre/M +Cassandry/M +Cassatt/M +Cassaundra/M +Cassey/M +Cassi/M +Cassie/M +Cassiopeia/M +Cassite/M +Cassius/M +Cassondra/M +Cassy/M +Castaneda/M +Castile's +Castillo/M +Castor/M +Castries/M +Castro/M +Catalan/MS +Catalina/M +Catalonia/M +Catarina/M +Catawba/M +Cate/M +Caterina/M +Caterpillar +Catha/M +Catharina/M +Catharine/M +Cathay/M +Cathe/RM +Cathee/M +Cather/M +Catherin/M +Catherina/M +Catherine/M +Cathi/M +Cathie/M +Cathleen/M +Cathlene/M +Catholic/S +Catholicism/SM +Cathrin/M +Cathrine/M +Cathryn/M +Cathy/M +Cathyleen/M +Cati/M +Catie/M +Catiline/M +Catina/M +Catlaina/M +Catlee/M +Catlin/M +Cato/M +Catrina/M +Catriona/M +Catskill/SM +Catt/M +Catullus/M +Caty/M +Caucasian/S +Caucasoid/S +Caucasus/M +Cauchy/M +Cavendish/M +Cavour/M +Caxton/M +Caye/M +Cayenne/M +Cayla/M +Cayman/M +Cayuga/M +Caz/M +Cazzie/M +Cb/M +Cchaddie/M +Cd/M +Ce +Ceausescu/M +Cebu/M +Cebuano/M +Cece/M +Cecelia/M +Cecil/M +Cecile/M +Ceciley/M +Cecilia/M +Cecilio/M +Cecilius/M +Cecilla/M +Cecily/M +Ced/M +Cedric/M +Ceil/M +Celanese/M +Cele/M +Celebes's +Celene/M +Celesta/M +Celeste/M +Celestia/M +Celestina/M +Celestine/M +Celestyn/M +Celestyna/M +Celia/M +Celie/M +Celina/M +Celinda/M +Celine/M +Celinka/M +Celisse/M +Celka/M +Celle/M +Cellini/M +Cello/M +Celsius/S +Celt/MS +Celtic/SM +Cenozoic +Centaurus/M +Centigrade +Centralia/M +Centrex +Cepheid +Cepheus/M +Cerberus/M +Cerenkov/M +Ceres/M +Cerf/M +Cervantes/M +Cesar/M +Cesare/M +Cesarean +Cesaro/M +Cessna/M +Cesya/M +Cetus/M +Ceylon/M +Ceylonese +Cezanne/S +Cf/M +Ch'in +Ch/MGNRS +Chablis/SM +Chad/M +Chadd/M +Chaddie/M +Chaddy/M +Chadian/S +Chadwick/M +Chaffey/M +Chagall/M +Chaim/M +Chaldea/M +Chaldean/M +Chalmers +Chamberlain/M +Chambers/M +Champlain/M +Chan/M +Chance/M +Chancellorsville/M +Chancey/M +Chanda/M +Chandal/M +Chandigarh/M +Chandler/M +Chandra/M +Chandragupta/M +Chandrasekhar/M +Chandy/M +Chane/M +Chanel/M +Chaney/M +Chang/M +Changchun/M +Changsha/M +Channa/M +Channing/M +Chantal/M +Chantalle/M +Chantilly/M +Chanukah's +Chao/M +Chaplin/M +Chapman/M +Chappaquiddick/M +Chara +Chardonnay +Charil/M +Charin/M +Chariot/M +Charis +Charissa/M +Charisse/M +Charita/M +Charity/M +Charla/M +Charlean/M +Charleen/M +Charlemagne/M +Charlena/M +Charlene/M +Charles/M +Charleston/SM +Charley/M +Charlie/M +Charline/M +Charlot/M +Charlotta/M +Charlotte/M +Charlottesville/M +Charlottetown/M +Charlton/M +Charmain/M +Charmaine/M +Charmane/M +Charmian/M +Charmin/M +Charmine/M +Charmion/M +Charo/M +Charolais +Charon/M +Chartres/M +Charybdis/M +Charyl/M +Chas +Chase/M +Chasity/M +Chastity/M +Chateaubriand +Chattahoochee/M +Chattanooga/M +Chatterley/M +Chatterton/M +Chaucer/M +Chaunce/M +Chauncey/M +Chautauqua/M +Chavez/M +Chayefsky/M +Che/M +Chechen/M +Chechnya/M +Cheddar/MS +Cheerios/M +Cheeto/M +Cheever/M +Chekhov/M +Chelsae/M +Chelsea/M +Chelsey/M +Chelsie/M +Chelsy/M +Chelyabinsk/M +Chen/M +Cheng/M +Chengdu +Cheops/M +Cher/M +Chere/M +Cherey/M +Cheri/M +Cherianne/M +Cherice/M +Cherida/M +Cherie/M +Cherilyn/M +Cherilynn/M +Cherin/M +Cherise/M +Cherish/M +Cheriton/M +Cherlyn/M +Chernenko/M +Chernobyl/M +Cherokee/MS +Cherri/M +Cherrita/M +Cherry/M +Chery/M +Cherye/M +Cheryl/M +Chesapeake/M +Cheshire/M +Cheslie/M +Chester/M +Chesterfield/M +Chesterton/M +Cheston/M +Chet/M +Chev/M +Chevalier/M +Cheviot/M +Chevrolet/M +Chevy/M +Cheyenne/SM +Chi/M +Chiang/M +Chianti/S +Chiarra/M +Chiba/M +Chic/M +Chicago/M +Chicagoan/SM +Chicana/MS +Chicano/MS +Chick/M +Chickasaw/SM +Chickie/M +Chicky/M +Chico/M +Chihuahua/MS +Chile/MS +Chilean/S +Chilton/M +Chimborazo/M +Chimera/S +Chimiques +Chimu/M +Chin/M +China/M +Chinaman/M +Chinamen +Chinatown/SM +Chinese/M +Ching/M +Chinook/MS +Chip/M +Chipewyan/M +Chippendale/M +Chippewa/MS +Chiquia/M +Chiquita/M +Chirico/M +Chisholm/M +Chisinau/M +Chittagong/M +Chlo/M +Chloe/M +Chloette/M +Chloris +Choctaw/MS +Chomsky/M +Chongqing +Chopin/M +Chou/M +Chretien/M +Chris/M +Chrisse/M +Chrissie/M +Chrissy/M +Christ/SMN +Christa/M +Christabel/M +Christabella/M +Christal/M +Christalle/M +Christan/M +Christchurch/M +Christean/M +Christel/M +Christen/M +Christendom/MS +Christensen/M +Christenson/M +Christi/M +Christian/MS +Christiana/M +Christiane/M +Christianity/SM +Christianize/GSD +Christiano/M +Christians/N +Christiansen/M +Christie/SM +Christin/M +Christina/M +Christine/M +Christlike +Christmas/SM +Christmastide/SM +Christmastime/S +Christoffel/M +Christoffer/M +Christoforo/M +Christoper/M +Christoph/MR +Christophe/M +Christopher/M +Christophorus/M +Christos/M +Christy's +Christye/M +Christyna/M +Chrisy/M +Chrotoem/M +Chrysa/M +Chrysler/M +Chrysostom/M +Chrystal/M +Chryste/M +Chrystel/M +Chucho/M +Chuck/M +Chukchi/M +Chumash/M +Chung/M +Chungking's +Church/MS +Churchill/M +Churchillian +Chuvash/M +Ci +Cicely/M +Cicero/M +Ciceronian +Cicily/M +Cid/M +Ciel/M +Cilka/M +Cincinnati/M +Cinda/M +Cindee/M +Cindelyn/M +Cinderella/MS +Cindi/M +Cindie/M +Cindra/M +Cindy/M +Cinerama/M +Cinnamon/M +Circe/M +Cirillo/M +Cirilo/M +Ciro/M +Cissiee/M +Cissy/M +Citibank/M +Citroen/M +Cl/VM +Claiborn/M +Claiborne/M +Clair/M +Claire/M +Clairol/M +Clancy/M +Clapeyron/M +Clapton/M +Clara/M +Clarabelle/M +Clarance/M +Clare/M +Claremont/M +Clarence/M +Clarendon/M +Claresta/M +Clareta/M +Claretta/M +Clarette/M +Clarey/M +Clari/M +Claribel/M +Clarice/M +Clarie/M +Clarinda/M +Clarine/M +Clarissa/M +Clarisse/M +Clarita/M +Clark/M +Clarke/M +Clarridge/M +Clary/M +Claude/M +Claudell/M +Claudelle/M +Claudetta/M +Claudette/M +Claudia/M +Claudian/M +Claudianus/M +Claudie/M +Claudina/M +Claudine/M +Claudio/M +Claudius/M +Claus/NM +Clausen/M +Clausewitz/M +Clausius/M +Clay/M +Clayborn/M +Clayborne/M +Claybourne/M +Clayson/M +Clayton/M +Clea/M +Clearwater/M +Cleavland/M +Clem/XM +Clemence/M +Clemenceau/M +Clement/MS +Clemente/M +Clementia/M +Clementina/M +Clementine/M +Clementius/M +Clemmie/M +Clemmy/M +Clemons +Clemson/M +Cleo/M +Cleon/M +Cleopatra/M +Clerc/M +Clerissa/M +Cletis +Cletus/M +Cleve/M +Cleveland/M +Clevey/M +Clevie/M +Cliburn/M +Cliff/M +Clifford/M +Clifton/M +Clim/M +Cline/M +Clint/M +Clinton/M +Clio/M +Clive/M +Clo/M +Cloe/M +Cloris/M +Clotho/M +Clotilda/M +Clovis/M +Cluj/M +Cly/M +Clyde/M +Clydesdale/M +Clytemnestra/M +Clyve/M +Clywd/M +Cm/M +Co/M +Coates/M +Cob/M +Cobain/M +Cobb/M +Cobbie/M +Cobby/M +Cobol/M +Cochabamba/M +Cochin/M +Cochise/M +Cochran/M +Cockney +Cocteau/M +Cod/M +Codee/M +Codi/M +Codie/M +Cody/M +Coffey/M +Coffman/M +Cognac/M +Cohan/M +Cohen/M +Cohn/M +Coimbatore/M +Cointon/M +Coke/MS +Col/MY +Colan/M +Colas +Colbert/M +Colby/M +Cole/M +Coleen/M +Coleman/M +Colene/M +Coleridge/M +Colet/M +Coletta/M +Colette/M +Colfax/M +Colgate/M +Colin/M +Colleen/M +Collen/M +Collete/M +Collette/M +Collie/M +Collier/M +Collin/MS +Colline/M +Colly/RM +Colman/M +Colo/M +Cologne/M +Colombia/M +Colombian/S +Colombo/M +Colon/M +Coloradan/S +Colorado/M +Coloradoan/S +Colosseum/M +Colt/M +Coltrane/M +Columbia/M +Columbian +Columbine/M +Columbus/M +Colver/M +Com/M +Comanche/MS +Combs/M +Comdex/M +Comdr/M +Cominform/M +Commie +Commons/M +Commonwealth/M +Commonwealths +Communion/SM +Communism/S +Communist/S +Comoros +Compaq/M +Compton/M +CompuServe/M +Compuserve/M +Comte/M +Con/M +Conakry/M +Conan/M +Conant/M +Concepción/M +Concetta/M +Concettina/M +Conchita/M +Concord/MS +Concorde/M +Concordia/M +Condorcet/M +Conestoga +Confederacy/M +Confederate/S +Confucian/S +Confucianism/SM +Confucius/M +Cong/M +Congo/M +Congolese +Congregational +Congregationalist/S +Congress/MS +Congreve/M +Conley/M +Conn/RM +Connecticut/M +Connelly/M +Conner/M +Connery/M +Conney/M +Conni/M +Connie/M +Connor/SM +Conny/M +Conrad/M +Conrade/M +Conrado/M +Conrail/M +Conroy/M +Consalve/M +Conservative/S +Consolata/M +Constable/M +Constance/M +Constancia/M +Constancy/M +Constanta/M +Constantia/M +Constantin/M +Constantina/M +Constantine/M +Constantino/M +Constantinople/M +Constitution +Consuela/M +Consuelo/M +Continent/M +Continental/S +Contreras/M +Conway/M +Cook/M +Cooke/M +Cookie/M +Cooley/M +Coolidge/M +Coop/MR +Cooper/M +Coors/M +Copeland/M +Copenhagen/M +Copernican +Copernicus/M +Copland/M +Copley/M +Copperfield/M +Coppola/M +Coptic/M +Cora/M +Corabel/M +Corabella/M +Corabelle/M +Coral/M +Coralie/M +Coraline/M +Coralyn/M +Corbet/M +Corbett/M +Corbie/M +Corbin/M +Corby/M +Cord/M +Cordelia/M +Cordelie/M +Cordell/M +Cordey/M +Cordi/M +Cordie/M +Cordilleras +Cordoba +Cordula/M +Cordy/M +Coreen/M +Corella/M +Corenda/M +Corene/M +Coretta/M +Corette/M +Corey/M +Corfu/M +Cori/M +Corie/M +Corilla/M +Corina/M +Corine/M +Corinna/M +Corinne/M +Corinth/M +Corinthian/S +Corinthians/M +Coriolanus/M +Coriolis/M +Coriss/M +Corissa/M +Cork/M +Corliss/M +Corly/M +Cormack/M +Cornall/M +Corneille/M +Cornela/M +Cornelia/M +Cornelius/M +Cornell/M +Cornelle/M +Corney/M +Cornie/M +Cornish/S +Cornwall/M +Cornwallis/M +Corny/M +Coronado/M +Corot/M +Corp +Correggio/M +Correna/M +Correy/M +Corri/M +Corrianne/M +Corrie/M +Corrina/M +Corrine/M +Corrinne/M +Corry/M +Corsica/M +Corsican/S +Cort/M +Cortes/S +Cortez's +Cortie/M +Cortland/M +Cortney/M +Corty/M +Corvallis/M +Corvus/M +Cory/M +Cos +Cosby/M +Cosetta/M +Cosette/M +Cosimo/M +Cosme/M +Cosmo/M +Cossack/SM +Costa/M +Costanza/M +Costello/M +Costner/M +Cote/M +Cotonou/M +Cotopaxi/M +Cotton/M +Coulomb/M +Couperin/M +Courbet/M +Court/M +Courtenay/M +Courtnay/M +Courtney/M +Cousteau/M +Covent/M +Coventry/MS +Coward/M +Cowley/M +Cowper/M +Cox/M +Coy/M +Cozmo/M +Cozumel/M +Cpl +Cr/M +Crabbe/M +Craft/M +Craggie/M +Craggy/M +Craig/M +Cramer/M +Cranach/M +Crandall/M +Crane/M +Cranford/M +Cranmer/M +Cranston/M +Crater/M +Crawford/M +Cray/SM +Crayola/M +Creation/M +Creator/M +Cree/MDS +Creek/SM +Creigh/M +Creight/M +Creighton/M +Creole/MS +Creon/M +Crestview/M +Cretaceous/Y +Cretaceously/M +Cretan/S +Crete/M +Crichton/M +Crick/M +Crimea/M +Crimean +Crin/M +Cris/M +Crisco/M +Crissie/M +Crissy/M +Crista/M +Cristabel/M +Cristal/M +Cristen/M +Cristi/M +Cristian/M +Cristiano/M +Cristie/M +Cristin/M +Cristina/M +Cristine/M +Cristionna/M +Cristobal/M +Cristy/M +Croat/SM +Croatia/M +Croatian/S +Croce/M +Crockett/M +Crockpot/M +Croesus/SM +Croix/M +Cromwell/M +Cromwellian +Cronin/M +Cronkite/M +Cronus/M +Crookes/M +Crosby/M +Cross/M +Crowley/M +Crucifixion/MS +Cruikshank/M +Crusoe/M +Crux/M +Cruz/M +Cryptozoic/M +Crysta/M +Crystal/M +Crystie/M +Cs +Ct/M +Cthrine/M +Cu/M +Cuba/M +Cuban/S +Cuchulain/M +Cuisinart/M +Culbertson/M +Cull/MN +Cullan/M +Cullen/M +Culley/M +Cullie/M +Cullin/M +Cully/M +Culver/MS +Cumberland/M +Cummings +Cunard/M +Cunningham/M +Cupertino/M +Cupid/M +Curacao/M +Curcio/M +Curie/M +Curitiba/M +Curr/M +Curran/M +Currey/M +Currie/M +Currier/M +Curry/MR +Curt/M +Curtice/M +Curtis/M +Cushman/M +Custer/M +Cuvier/M +Cuzco/M +Cy/M +Cyanamid/M +Cyb/M +Cybele/M +Cybil/M +Cybill/M +Cyclades +Cyclopes +Cyclops/M +Cygnus/M +Cymbre/M +Cynde/M +Cyndi/M +Cyndia/M +Cyndie/M +Cyndy/M +Cynthea/M +Cynthia/M +Cynthie/M +Cynthy/M +Cyprian +Cypriot/SM +Cyprus/M +Cyrano/M +Cyril/M +Cyrill/M +Cyrille/M +Cyrillic +Cyrillus/M +Cyrus/M +Czech +Czechoslovak/S +Czechoslovakia/M +Czechoslovakian/S +Czechs +Czerniak/M +Czerny/M +D +D'Arcy +D's +DA +DAG +DARPA/M +DAT +DB +DBMS +DC +DD +DDS +DDT +DE +DEC/M +DECNET +DECnet/M +DECstation/M +DECsystem/M +DECtape/M +DH +DI +DJ +DMD +DMZ +DNA +DOA +DOB +DOD +DOE +DOS +DOT +DP +DPT +DPs +DST +DTP +DUI +DWI +Dacca's +Dacey/M +Dachau/M +Dacia/M +Dacie/M +Dacron/MS +Dacy/M +Dada/M +Dadaism/M +Dadaist/M +Dade/M +Daedalus/M +Dael/M +Daffi/M +Daffie/M +Daffy/M +Dag/M +Dagmar/M +Dagny/M +Daguerre/M +Dagwood/M +Dahl/M +Dahlia/M +Dahomey/M +Daile/M +Daimler/M +Daisey/M +Daisi/M +Daisie/M +Daisy/M +Dakar/M +Dakota/SM +Dakotan +Dal/M +Dale/M +Dalenna/M +Daley/M +Dalhousie/M +Dali/SM +Dalia/M +Dalian/M +Dalila/M +Dall/M +Dallas/M +Dalli/MS +Dallon/M +Dalmatia/M +Dalmatian/SM +Daloris/M +Dalston/M +Dalt/M +Dalton/M +Daly/M +Damara/M +Damaris/M +Damascus/M +Dame/SMN +Damian/M +Damiano/M +Damien/M +Damion/M +Damita/M +Damocles/M +Damon/M +Dan/SM +Dana/M +Danaë +Danbury/M +Dane/SM +Danelaw/M +Danell/M +Danella/M +Danette/M +Dangerfield/M +Dani/M +Dania/M +Danial/M +Danica/M +Danice/M +Danie/M +Daniel/SM +Daniela/M +Daniele/M +Daniella/M +Danielle/M +Danielson/M +Danika/M +Danila/M +Danish +Danit/M +Danita/M +Danna/M +Dannel/M +Danni/M +Dannie/M +Danny/M +Dannye/M +Dante/M +Danton/M +Danube/M +Danubian +Danville/M +Danya/M +Danyelle/M +Danyette/M +Danzig/M +Daphene/M +Daphna/M +Daphne/M +Dar/MNH +Dara/M +Darb/M +Darbee/M +Darbie/M +Darby/M +Darcee/M +Darcey/M +Darci/M +Darcie/M +Darcy/M +Darda/M +Dardanelles +Dare/M +Dareen/M +Darell/M +Darelle/M +Daren/M +Dari/M +Daria/M +Darice/M +Darill/M +Darin/M +Dario/M +Darius/M +Darjeeling/M +Darla/M +Darleen/M +Darlene/M +Darline/M +Darling/M +Darlington/M +Darlleen/M +Darn/M +Darnall/M +Darnell/M +Daron/M +Darrel/M +Darrell/M +Darrelle/M +Darren/M +Darrick/M +Darrin/M +Darrow/M +Darryl/M +Darsey/M +Darsie/M +Darth/M +Dartmouth/M +Darvon/M +Darwin/M +Darwinian/S +Darwinism/MS +Darwinist/MS +Darya/M +Daryl/M +Daryle/M +Daryn/M +Dasha/M +Dasi/M +Dasie/M +Dasya/M +Datamation/M +Datamedia/M +Datha/M +Datsun/M +Daugherty/M +Daumier/M +Daune/M +Dav/MN +Davao/M +Dave/M +Daveen/M +Daven/M +Davenport/M +Daveta/M +Davey/M +David/SM +Davida/M +Davidde/M +Davide/M +Davidson/M +Davie/M +Davin/M +Davina/M +Davine/M +Davinich/M +Davis/M +Davita/M +Davon/M +Davy/SM +Dawes/M +Dawn/M +Dawna/M +Dawson/M +Day/M +Dayle/M +Dayna/M +Dayton/M +Ddene/M +De/NM +DeKalb/M +DeKastere/M +DeMorgan/M +Dean/M +Deana/M +Deandre/M +Deane/M +Deann/M +Deanna/M +Deanne/M +Dearborn/M +Deb/MS +Debbi/M +Debbie/M +Debby/M +Debee/M +Debera/M +Debi/M +Debian +Debian's +Debor/M +Debora/M +Deborah/M +Debra/M +Debussy/M +Dec/M +Decalogue/M +Decatur/M +Decca/M +Deccan/M +December/SM +Deck/RM +Decker/M +Dede/M +Dedekind/M +Dedie/M +Dedra/M +Dee/M +Deeann/M +Deeanne/M +Deedee/M +Deena/M +Deerdre/M +Deere/M +Deeyn/M +Defoe/M +Degas/M +Dehlia/M +Deidre/M +Deimos/M +Deina/M +Deirdre/MS +Deity/M +Dejesus/M +Del/MY +Dela/M +Delacroix/M +Delacruz/M +Delainey/M +Delaney/M +Delano/M +Delaware/MS +Delawarean/SM +Delbert/M +Delcina/M +Delcine/M +Deleon/M +Delft/M +Delgado/M +Delhi/M +Delia/M +Delibes/M +Delila/M +Delilah/M +Delilahs +Delinda/M +Delius/M +Dell/M +Della/M +Dellwood/M +Delly/M +Delmar/M +Delmarva/M +Delmer/M +Delmonico +Delmor/M +Delmore/M +Delora/M +Delores/M +Deloria/M +Deloris/M +Delphi/M +Delphic +Delphine/M +Delphinia/M +Delphinus/M +Delta/M +Dem/MG +Demavend/M +Demerol/M +Demeter/M +Demetra/M +Demetre/M +Demetri/MS +Demetria/M +Demetrius/M +Deming/M +Democrat/MS +Democratic +Democritus/M +Demosthenes/M +Demott/M +Dempsey/M +Den/M +Dena/M +Dene/M +Deneb/M +Denebola/M +Deneen/M +Deng/M +Deni/SM +Denice/M +Denise/M +Denmark/M +Denna/M +Dennet/M +Denney/M +Denni/MS +Dennie/M +Dennison/M +Denny/M +Denver/M +Deny/M +Denys +Denyse/M +Deon/M +Deonne/M +Dependant/MS +Dept/M +Der/M +Derby/SM +Derbyshire/M +Derek/M +Derick/M +Derk/M +Dermot/M +Derrek/M +Derrick/M +Derrida/M +Derrik/M +Derril/M +Derron/M +Derry/M +Derward/M +Derwin/M +Des +Descartes/M +Desdemona/M +Desi/M +Desirae/M +Desiree/M +Desiri/M +Desmond/M +Desmund/M +Detroit/M +Deuteronomy/M +Deutsch/M +Dev/M +Deva/M +Devan/M +Devanagari/M +Devi/M +Devin/M +Devina/M +Devinne/M +Devland/M +Devlen/M +Devlin/M +Devon/M +Devondra/M +Devonian +Devonna/M +Devonne/M +Devonshire/M +Devora/M +Devy/M +Dew/M +Dewain/M +Dewar/M +Dewayne/M +Dewey/M +Dewie/M +Dewitt/M +Dex/M +Dexedrine/M +Dexter/M +Dhaka +Dhaulagiri/M +Di/M +DiCaprio/M +DiMaggio/M +Diaghilev/M +Diahann/M +Dian/M +Diana/M +Diandra/M +Diane/M +Dianemarie/M +Diann/M +Dianna/M +Dianne/M +Diannne/M +Diarmid/M +Diaspora/SM +Diaz's +Dick/XM +Dickens/M +Dickensian/S +Dickerson/M +Dickie/M +Dickinson/M +Dickson/M +Dicky/M +Dictaphone/SM +Diderot/M +Didi/M +Dido/M +Diefenbaker/M +Diego/M +Diem/M +Diena/M +Dierdre/M +Diesel's +Dieter/M +Dietrich/M +Dietz/M +Dijkstra/M +Dijon/M +Dilan/M +Dilbert/M +Dill/M +Dillard/M +Dillie/M +Dillinger/M +Dillon/M +Dilly/M +Dimitri/M +Dimitry/M +Dina/M +Dinah/M +Dinnie/M +Dinny/M +Dino/M +Diocletian/M +Diogenes/M +Dion/M +Dione/M +Dionis/M +Dionisio/M +Dionne/M +Dionysian +Dionysus/M +Diophantine/M +Dior/M +Dipper/M +Dir +Dirac/M +Dirichlet/M +Dirk/M +Dis +Disney/M +Disneyland/M +Disraeli/M +Dita/M +Ditzel/M +Dix/M +Dixie/M +Dixiecrat/MS +Dixieland/MS +Dixon/M +Djakarta's +Djibouti/M +Dmitri/M +Dnepr's +Dnepropetrovsk/M +Dnieper's +Dniester/M +Dniren/M +Dobbin/M +Doberman +Dobro/M +Doctor +Doctorow/M +Dode/M +Dodge/M +Dodgson/M +Dodi/M +Dodie/M +Dodington/M +Dodoma/M +Dodson/M +Dody/M +Doe/M +Doge/M +Dogtown/M +Doha/M +Dolby/SM +Dole/M +Dolf/M +Doll/M +Dolley/M +Dolli/M +Dollie/M +Dolly/M +Dolores/M +Dolorita/SM +Dolph/M +Dom/M +Domenic/M +Domenico/M +Domeniga/M +Domesday/M +Dominga/M +Domingo/M +Dominguez/M +Domini/M +Dominic/M +Dominica/M +Dominican/MS +Dominick/M +Dominik/M +Dominique/M +Domitian/M +Don/SM +Dona/M +Donahue/M +Donal/M +Donald/M +Donaldson/M +Donall/M +Donalt/M +Donatello/M +Donaugh/M +Donavon/M +Donella/M +Donelle/M +Donetsk/M +Donetta/M +Donia/M +Donica/M +Donielle/M +Donizetti/M +Donn/RM +Donna/M +Donnamarie/M +Donne/M +Donnell/M +Donnelly/M +Donner/M +Donni/M +Donnie/M +Donny/M +Donovan/M +Dooley/M +Doolittle/M +Doonesbury/M +Doppler/M +Dora/M +Dorado/M +Doralia/M +Doralin/M +Doralyn/M +Doralynn/M +Doralynne/M +Dorcas +Dorchester/M +Doreen/M +Dorelia/M +Dorella/M +Dorelle/M +Dorena/M +Dorene/M +Doretta/M +Dorette/M +Dorey/M +Dori/MS +Doria/M +Dorian/M +Doric +Dorice/M +Dorie/M +Dorine/M +Dorisa/M +Dorise/M +Dorita/M +Doro/M +Dorolice/M +Dorolisa/M +Dorotea/M +Doroteya/M +Dorothea/M +Dorothee/M +Dorothy/M +Dorree/M +Dorri/SM +Dorrie/M +Dorry/M +Dorsey/M +Dorthea/M +Dorthy/M +Dortmund/M +Dory/M +Doré/M +Dosi/M +Dostoevsky/M +Dot/M +Doti/M +Dotson/M +Dotti/M +Dottie/M +Dotty/M +Douala/M +Douay/M +Doubleday/M +Doug/M +Dougherty/M +Dougie/M +Douglas/M +Douglass +Dougy/M +Douro/M +Dov/MR +Dover/M +Dow/M +Downey/M +Downs +Doy/M +Doyle/M +Dr/M +Draco/M +Draconian +Dracula/M +Drake/M +Dramamine/MS +Drambuie/M +Drano/M +Dravidian/M +Dre/M +Dreddy/M +Dredi/M +Dreiser/M +Dresden/M +Drew/M +Drexel/M +Dreyfus/M +Dreyfuss +Drona/M +Dru/M +Druci/M +Drucie/M +Drucill/M +Drucy/M +Drud/M +Drugi/M +Druid's +Drummond/M +Drury/M +Drusi/M +Drusie/M +Drusilla/M +Drusy/M +Dryden/M +Dshubba/M +Du/M +DuPont/MS +Duane/M +Dubai/M +Dubcek/M +Dubhe/M +Dublin/M +Dubrovnik/M +Dubuque/M +Duchamp/M +Dud/M +Dudley/M +Duff/M +Duffie/M +Duffy/M +Dugald/M +Duisburg/M +Duke/M +Dukey/M +Dukie/M +Duky/M +Dulce/M +Dulcea/M +Dulci/M +Dulcia/M +Dulciana/M +Dulcie/M +Dulcine/M +Dulcinea/M +Dulcy/M +Dulles/M +Dulsea/M +Duluth/M +Dumas +Dumbo/M +Dumont/M +Dumpster/S +Dumpty/M +Dun/M +Dunant/M +Dunbar/M +Dunc/M +Duncan/M +Dundee/M +Dunedin/M +Dunham/M +Dunkirk/M +Dunlap/M +Dunn/M +Dunne/M +Dunstan/M +Dupont/MS +Dur/M +Duracell/M +Duran/M +Durand/M +Durant/M +Durante/M +Durban/M +Durex/M +Durham/MS +Durkee/M +Durkheim/M +Durocher/M +Durward/M +Duse/M +Dusenberg/M +Dusenbury/M +Dushanbe/M +Dustin/M +Dusty/M +Dutch/M +Dutchman/M +Dutchmen +Dutchwoman +Dutchwomen +Duvalier/M +Dvina/M +Dvorák/M +Dwain/M +Dwayne/M +Dwight/M +Dy/M +Dyan/M +Dyana/M +Dyane/M +Dyann/M +Dyanna/M +Dyanne/M +Dyer/M +Dyke/M +Dylan/M +Dyna/M +Dynah/M +Dzerzhinsky/M +Dürer/M +Düsseldorf +E +E's +EBCDIC +EC +ECG +EDP +EDT +EEC +EEG +EEO +EEOC +EFL +EFT +EGA/M +EKG +EM +EMT +ENE +EOE +EPA +ER +ERA +ESE +ESL +ESP +EST +ET +ETA +ETD +EU +Eachelle/M +Eada/M +Eadie/M +Eadith/M +Eadmund/M +Eakins/M +Eal/M +Ealasaid/M +Eamon/M +Earhart/M +Earl/M +Earle/M +Earlene/M +Earlie/M +Earline/M +Early/M +Earnest/M +Earnestine/M +Earp/M +Eartha/M +Earvin/M +East/ZSMR +Easter/M +Eastern/RZ +Easterner/M +Easthampton/M +Eastland/M +Eastman/M +Eastwick/M +Eastwood/M +Eaton/M +Eb/MN +Eba/M +Ebba/M +Eben/M +Ebeneezer/M +Ebeneser/M +Ebenezer/M +Eberhard/M +Eberto/M +Ebola +Ebonee/M +Ebonics +Ebony/M +Ebro/M +Eccles +Ecclesiastes/M +Eco/M +Ecole/M +Econometrica/M +Ecstasy/S +Ecuador/M +Ecuadoran/S +Ecuadorean/S +Ecuadorian/S +Ed/XMN +Eda/M +Edam/SM +Edan/M +Edd/M +Edda/M +Eddi/M +Eddie/M +Eddy/M +Ede/M +Edee/M +Edeline/M +Eden/M +Edgar/M +Edgard/M +Edgardo/M +Edgerton/M +Edgewater/M +Edgewood/M +Edi/MH +Edie/M +Edik/M +Edin/M +Edinburgh/M +Edison/M +Edita/M +Edith/M +Editha/M +Edithe/M +Ediva/M +Edlin/M +Edmon/M +Edmond/M +Edmonton/M +Edmund/M +Edna/M +Edouard/M +Edsel/M +Edsger/M +Eduard/M +Eduardo/M +Edubuntu +Edubuntu's +Eduino/M +Edvard/M +Edward/SM +Edwardian +Edwardo/M +Edwin/M +Edwina/M +Edy/M +Edyth/M +Edythe/M +Eeyore/M +Effie/M +Efrain/M +Efrem/M +Efren/M +Egan/M +Egbert/M +Egerton/M +Egon/M +Egor/M +Egypt/M +Egyptian/S +Egyptology/M +Ehrlich/M +Eichmann/M +Eiffel/M +Eileen/M +Eilis/M +Eimile/M +Einstein/SM +Einsteinian +Eire/M +Eirena/M +Eisenhower/M +Eisenstein/M +Eisner/M +Ekaterina/M +Ekberg/M +Ekstrom/M +Ektachrome/M +El/MY +Elaina/M +Elaine/M +Elana/M +Elane/M +Elanor/M +Elayne/M +Elba/MS +Elbe/M +Elbert/M +Elberta/M +Elbertina/M +Elbertine/M +Elbrus/M +Elden/M +Eldin/M +Eldon/M +Eldorado's +Eldredge/M +Eldridge/M +Eleanor/M +Eleanora/M +Eleanore/M +Eleazar/M +Electra/M +Eleen/M +Elena/M +Elene/M +Eleni/M +Elenore/M +Eleonora/M +Eleonore/M +Elfie/M +Elfreda/M +Elfrida/M +Elfrieda/M +Elga/M +Elgar/M +Eli/M +Elia/SM +Elianora/M +Elianore/M +Elicia/M +Elie/M +Elihu/M +Elijah/M +Elinor/M +Elinore/M +Eliot/M +Elisa/M +Elisabet/M +Elisabeth/M +Elisabetta/M +Elise/M +Eliseo/M +Elisha/M +Elissa/M +Elita/M +Eliza/M +Elizabet/M +Elizabeth/M +Elizabethan/S +Elka/M +Elke/M +Elkhart/M +Ella/M +Elladine/M +Ellary/M +Elle/M +Ellen/M +Ellene/M +Ellerey/M +Ellery/M +Ellesmere/M +Ellette/M +Elli/SM +Ellie/M +Ellington/M +Elliot/M +Elliott/M +Ellison/M +Ellissa/M +Ellswerth/M +Ellsworth/M +Ellwood/M +Elly/M +Ellyn/M +Ellynn/M +Elma/M +Elmer/M +Elmhurst/M +Elmira/M +Elmo/M +Elmore/M +Elmsford/M +Elna/MH +Elnar/M +Elnath/M +Elnora/M +Elnore/M +Elohim/M +Eloisa/M +Eloise/M +Elonore/M +Elora/M +Eloy/M +Elroy/M +Elsa/M +Elsbeth/M +Else/M +Elset/M +Elsey/M +Elsi/M +Elsie/M +Elsinore/M +Elspeth/M +Elston/M +Elsworth/M +Elsy/M +Eltanin/M +Elton/M +Elva/M +Elvera/M +Elvia/M +Elvin/M +Elvina/M +Elvira/M +Elvis/M +Elvyn/M +Elwin/M +Elwira/M +Elwood/M +Elwyn/M +Ely/M +Elyn/M +Elyse/M +Elysees +Elysha/M +Elysia/M +Elysian +Elysium/SM +Elyssa/M +Elysée/M +Em/M +Ema/M +Emacs/M +Emalee/M +Emalia/M +Emanuel/M +Emanuele/M +Emelda/M +Emelen/M +Emelia/M +Emelina/M +Emeline/M +Emelita/M +Emelyne/M +Emera/M +Emerson/M +Emery/M +Emil/M +Emile/M +Emilee/M +Emili/M +Emilia/M +Emilie/M +Emiline/M +Emilio/M +Emily/M +Eminence/MS +Emlen/M +Emlyn/M +Emlynn/M +Emlynne/M +Emma/M +Emmalee/M +Emmaline/M +Emmalyn/M +Emmalynn/M +Emmalynne/M +Emmanuel/M +Emmeline/M +Emmerich/M +Emmery/M +Emmet/M +Emmett/M +Emmey/M +Emmi/M +Emmie/M +Emmit/M +Emmott/M +Emmy/SM +Emmye/M +Emogene/M +Emory/M +Emyle/M +Emylee/M +Endicott/M +Endymion/M +Eng/M +Engel/MS +Engelbert/M +England/M +Englebert/M +Englewood/M +English/GDRSM +Englishman/M +Englishmen +Englishwoman/M +Englishwomen +Engracia/M +Enid/M +Enif/M +Eniwetok/M +Enkidu/M +Ennis/M +Enoch/M +Enos +Enrica/M +Enrichetta/M +Enrico/M +Enrika/M +Enrique/M +Enriqueta/M +Ensolite/M +Enterprise/M +Eocene +Eolanda/M +Eolande/M +Ephesian/S +Ephesians/M +Ephesus/M +Ephraim/M +Ephrayim/M +Ephrem/M +Epictetus/M +Epicurean +Epicurus/M +Epimethius/M +Epiphany/SM +Episcopal/S +Episcopalian/S +Epistle/SM +Epsom/M +Epstein/M +Equuleus/M +Er/M +Eran/M +Erasmus/M +Erastus/M +Erato/M +Eratosthenes/M +Erda/M +Erebus/M +Erek/M +Erena/M +Erhard/M +Erhart/M +Eric/M +Erica/M +Erich/M +Ericha/M +Erick/M +Ericka/M +Erickson/M +Ericson's +Ericsson's +Eridanus/M +Erie/SM +Erik/M +Erika/M +Erikson/M +Erin/M +Erina/M +Erinn/M +Erinna/M +Eris +Eritrea/M +Erl/M +Erlang/M +Erlenmeyer/M +Erma/M +Ermanno/M +Ermengarde/M +Ermentrude/M +Ermin/M +Ermina/M +Erminia/M +Erminie/M +Erna/M +Ernaline/M +Ernest/M +Ernesta/M +Ernestine/M +Ernesto/M +Ernestus/M +Ernie/M +Ernst/M +Erny/M +Eros/SM +Errick/M +Errol/M +Erroll/M +Erse/M +Erskine/M +Ertha/M +Erv/M +ErvIn/M +Ervin/M +Erwin/M +Eryn/M +Es +Esau/M +Escher/M +Escherichia/M +Escondido/M +Esdras/M +Eskimo/SM +Esma/M +Esmaria/M +Esmark/M +Esme/M +Esmeralda/M +Esp/M +Espagnol/M +Esperanto/M +Esperanza/M +Espinoza/M +Esposito/M +Esq/M +Esquire/S +Esra/M +Essa/M +Essen/M +Essene/SM +Essequibo/M +Essex/M +Essie/M +Essy/M +Esta/M +Establishment/MS +Esteban/M +Estel/M +Estela/M +Estele/M +Estell/M +Estella/M +Estelle/M +Ester/M +Esterházy/M +Estes +Estevan/M +Esther/M +Estonia/M +Estonian/S +Estrada/M +Estrella/M +Estrellita/M +Etan/M +Ethan/M +Ethe/M +Ethel/M +Ethelbert/M +Ethelda/M +Ethelin/M +Ethelind/M +Etheline/M +Ethelred/M +Ethelyn/M +Ethernet/MS +Ethiopia/M +Ethiopian/S +Ethyl/M +Etienne/M +Etna/M +Etruria/M +Etruscan/MS +Etta/M +Etti/M +Ettie/M +Ettore/M +Etty/M +Eu/M +Eucharist/SM +Eucharistic +Euclid/M +Eudora/M +Euell/M +Eugen/M +Eugene/M +Eugenia/M +Eugenie/M +Eugenio/M +Eugenius/M +Eugine/M +Eula/M +Eulalie/M +Euler/M +Eulerian/M +Eumenides +Eunice/M +Euphemia/M +Euphrates/M +Eur/M +Eurasia/M +Eurasian/S +Euripides/M +Eurodollar/SM +Europa/M +Europe/M +European/MS +Europeanization/SM +Europeanized +Eurydice/M +Eustace/M +Eustachian/M +Eustacia/M +Euterpe/M +Ev/MN +Eva/M +Evaleen/M +Evan/MS +Evangelia/M +Evangelical/S +Evangelin/M +Evangelina/M +Evangeline/M +Evangelist/MS +Evania/M +Evanne/M +Evanston/M +Evansville/M +Eve/M +Eveleen/M +Evelin/M +Evelina/M +Eveline/M +Evelyn/M +Even/M +Evenki/M +EverReady/M +Everard/M +Eveready/M +Evered/M +Everest/M +Everett/M +Everette/M +Everglades +Everhart/M +Evey/M +Evie/M +Evin/M +Evita/M +Evonne/M +Evvie/M +Evvy/M +Evy/M +Evyn/M +Ewan/M +Eward/M +Ewart/M +Ewell/M +Ewen/M +Ewing/M +Excalibur/M +Excedrin/M +Excellency/MS +Exchequer/SM +Exeter/M +Exodus/M +Exxon/M +Eyck/M +Eyde/M +Eydie/M +Eyre/M +Eysenck/M +Ezechiel/M +Ezekiel/M +Ezequiel/M +Eziechiele/M +Ezmeralda/M +Ezra/M +Ezri/M +F +F's +FAA +FAQ/SM +FBI +FCC +FD +FDA +FDIC +FDR/M +FHA +FICA +FIFO +FL +FM +FNMA/M +FOFL +FORTH/M +FORTRAN +FPO +FSLIC +FTC +FTP +FUD +FWD +FY +FYI +Fabe/MR +Faber/M +Fabergé/M +Fabian/S +Fabiano/M +Fabien/M +Fabio/M +Fae/M +Faeroe/M +Fafnir/M +Fagin/M +Fahd/M +Fahrenheit/S +Faina/M +Fair/M +Fairbanks +Fairchild/M +Fairfax/M +Fairfield/M +Fairleigh/M +Fairlie/M +Fairmont/M +Fairport/M +Fairview/M +Faisal/M +Faisalabad +Faith/M +Falito/M +Falk/M +Falkland/MS +Falkner/M +Fallon/M +Fallopian/M +Falstaff/M +Falwell/M +Fan/M +Fanchette/M +Fanchon/M +Fancie/M +Fancy/M +Fanechka/M +Fania/M +Fanni/M +Fannie/M +Fanny/SM +Fanya/M +Far/MY +Fara/M +Faraday/M +Farah/M +Farand/M +Farber/M +Fargo/M +Farica/M +Farkas/M +Farlay/M +Farlee/M +Farleigh/M +Farley/M +Farlie/M +Farly/M +Farmer/M +Farmington/M +Farr/M +Farra/M +Farragut/M +Farrah/M +Farrakhan/M +Farrand/M +Farrel/M +Farrell/M +Farris/M +Fascism's +Fascist's +Fassbinder/M +Fates +Father/SM +Fatima/M +Faulkner/M +Faulknerian +Faun/M +Faunie/M +Fauntleroy/M +Faust/M +Faustian +Faustina/M +Faustine/M +Faustino/M +Faustus/M +Fawkes/M +Fawn/M +Fawne/M +Fawnia/M +Fax/M +Fay/M +Faydra/M +Faye/M +Fayette/M +Fayetteville/M +Fayina/M +Fayre/M +Fayth/M +Faythe/M +Fe/M +Featherman/M +Feb/M +February/MS +Fed/SM +FedEx/M +Federal/S +Federalist +Federica/M +Federico/M +Fedora/M +Fee/M +Felder/M +Feldman/M +Felecia/M +Felic/M +Felicdad/M +Felice/M +Felicia/M +Felicio/M +Felicity/M +Felicle/M +Felike/M +Feliks/M +Felipa/M +Felipe/M +Felisha/M +Felita/M +Felix/M +Feliza/M +Felizio/M +Fellini/M +Fenelia/M +Fenian/M +Fenwick/M +Feodor/M +Feodora/M +Ferber/M +Ferd/M +Ferdie/M +Ferdinand/M +Ferdinanda/M +Ferdinande/M +Ferdinando/M +Ferdy/M +Fergus/M +Ferguson/M +Ferlinghetti/M +Fermat/M +Fermi/M +Fern/M +Fernanda/M +Fernande/M +Fernandez/M +Fernandina/M +Fernando/M +Ferne/M +Ferrari/M +Ferraro/M +Ferreira/M +Ferrel/M +Ferrell/M +Ferrer/M +Ferris +Fess/M +Fey/M +Feynman/M +Fez/M +Fiann/M +Fianna/M +Fiat/M +Fiberglas/M +Fibonacci/M +Fichte/M +Fidel/M +Fidela/M +Fidelia/M +Fidelio/M +Fidelity/M +Fido/M +Fidole/M +Field/MGS +Fielding/M +Fifi/M +Fifine/M +Figaro/M +Figueroa/M +Fiji/M +Fijian/SM +Filbert/M +Filberte/M +Filberto/M +Filia/M +Filide/M +Filip/M +Filipino/SM +Filippa/M +Filippo/M +Fillmore/M +Filmer/M +Filmore/M +Filofax/S +Fin/M +Fina/M +Finch/M +Findlay/M +Findley/M +Finland/M +Finlay/M +Finley/M +Finn/MS +Finnbogadottir/M +Finnegan/M +Finnish +Fiona/M +Fionna/M +Fionnula/M +Fiorello/M +Fiorenze/M +Fiori/M +Firefox/M +Firestone/M +Fischbein/M +Fischer/M +Fisher/M +Fishkill/M +Fisk/M +Fiske/M +Fitch/M +Fitchburg/M +Fitz/M +Fitzgerald/M +Fitzpatrick/M +Fitzroy/M +Fizeau/M +Fla/M +Flanagan/M +Flanders/M +Flatt/M +Flaubert/M +Fledermaus/M +Fleischer/M +Fleischman/M +Fleisher/M +Flem/JGM +Fleming/M +Flemish/GDSM +Flemished/M +Flemishing/M +Flemming/M +Fletch/MR +Fletcher/M +Fleur/M +Fleurette/M +Flin/M +Flinn/M +Flint/M +Flintstones +Flo/M +Flor/M +Flora/M +Florance/M +Flore/SM +Florella/M +Florence/M +Florencia/M +Florentia/M +Florentine/S +Florenza/M +Florette/M +Flori/SM +Floria/M +Florian/M +Florida/M +Floridan/S +Floridian/S +Florie/M +Florina/M +Florinda/M +Florine/M +Florri/M +Florrie/M +Florry/M +Flory/M +Flossi/M +Flossie/M +Flossy/M +Flowers +Floyd/M +Flss/M +Flynn/M +Fm/M +Foch/M +Fokker/M +Foley/M +Folsom +Fomalhaut/M +Fonda/M +Fons +Fonsie/M +Fontaine/M +Fontainebleau/M +Fontana/M +Fonz/M +Fonzie/M +Foote/M +Forbes/M +Ford/M +Fordham/M +Foreman/M +Forest/MR +Forester/M +Formica/MS +Formosa/M +Formosan +Forrest/RM +Forrester/M +Forster/M +Fortaleza/M +Fortran/M +Foss/M +Foster/M +Foucault/M +Fourier/M +Fourth +Fourths +Fowler/M +Fox/MS +Foxhall/M +Fr/MD +Fragonard/M +Fran/MS +Francaise/M +France/MS +Francene/M +Francesca/M +Francesco/M +Franchot/M +Francie/M +Francine/M +Francis +Francisca/M +Franciscan/MS +Francisco/M +Franciska/M +Franciskus/M +Franck/M +Francklin/M +Francklyn/M +Franco/M +Francois/M +Francoise/M +Francyne/M +Frank/SM +Frankel/M +Frankenstein/MS +Frankford/M +Frankfort/M +Frankfurt/RM +Frankfurter/M +Frankie/M +Frankish/M +Franklin/M +Franklyn/M +Franky/M +Franni/M +Frannie/M +Franny/M +Fransisco/M +Frants/M +Franz/NM +Franzen/M +Frasco/M +Fraser/M +Frasier/M +Frasquito/M +Frau/MN +Fraulein/S +Frayda/M +Frayne/M +Fraze/MR +Frazer/M +Frazier/M +Fred/M +Freda/M +Freddi/M +Freddie/M +Freddy/M +Fredek/M +Fredelia/M +Frederic/M +Frederica/M +Frederich/M +Frederick/MS +Fredericka/M +Frederico/M +Fredericton/M +Frederigo/M +Frederik/M +Frederique/M +Fredholm/M +Fredi/M +Fredia/M +Fredra/M +Fredric/M +Fredrick/M +Fredrickson/M +Fredrika/M +Free/M +Freedman/M +Freeland/M +Freeman/M +Freemason/SM +Freemasonry/MS +Freemon/M +Freeport/M +Freetown/M +Freida/M +Fremont/M +French/MDSG +Frenchman/M +Frenchmen +Frenchwoman/M +Frenchwomen +Freon/SM +Fresnel/M +Fresno/M +Freud/M +Freudian/S +Frey/M +Freya/M +Fri/M +Frick/M +Friday/SM +Frieda/M +Friedan/M +Friederike/M +Friedman/M +Friedrich/M +Friedrick/M +Frigga/M +Frigidaire/M +Frisbee/MS +Frisco/M +Frisian/SM +Frito/M +Fritz/M +Frobisher/M +Froissart/M +Fromm/M +Frontenac/M +Frost/M +Frostbelt/M +Fruehauf/M +Frunze/M +Fry/M +Frye/M +Fuchs/M +Fuentes/M +Fugger/M +Fuji/M +Fujitsu/M +Fujiyama +Fukuoka/M +Fulani/M +Fulbright/M +Fuller/M +Fullerton/M +Fulton/M +Fulvia/M +Funafuti +Fundy/M +Fushun/M +Fuzhou/M +Fuzzbuster/M +G's +G/B +GA +GAO +GB +GDP +GE/M +GED +GHQ +GHz +GI +GIGO +GM +GMT +GNOME/M +GNP +GOP +GOTO/MS +GP +GPA +GPO +GPSS +GSA +GU +GUI +Ga/M +Gabbey/M +Gabbi/M +Gabbie/M +Gabby/M +Gabe/M +Gabey/M +Gabi/M +Gabie/M +Gable/M +Gabon/M +Gabonese +Gaborone/M +Gabriel/M +Gabriela/M +Gabriele/M +Gabriell/M +Gabriella/M +Gabrielle/M +Gabriellia/M +Gabriello/M +Gabrila/M +Gaby/M +Gacrux/M +Gadsden/M +Gae/M +Gaea/M +Gael/SM +Gaelan/M +Gaelic/M +Gagarin/M +Gage/M +Gail/M +Gaile/M +Gaines/M +Gainesville/M +Gainsborough/M +Gaithersburg/M +Gal/MN +Galahad/MS +Galapagos/M +Galatea/M +Galatia/M +Galatians/M +Galaxy/M +Galbraith/M +Galbreath/M +Gale/M +Galen/M +Galibi/M +Galilean/MS +Galilee/M +Galileo/M +Galina/M +Gall/M +Gallagher/M +Gallard/M +Gallegos/M +Gallic +Gallicism/SM +Galloway/M +Gallup/M +Galois/M +Galsworthy/M +Galvan/M +Galvani/M +Galven/M +Galveston/M +Galvin/M +Gama/M +Gamaliel/M +Gambia/M +Gambian/S +Gamble/M +Gamow/M +Gan/M +Gandhi/M +Gandhian +Ganges/M +Gangtok/M +Gannie/M +Gannon/M +Ganny/M +Gantry/M +Ganymede/M +Gar/MH +Garald/M +Garbo/M +Garcia/M +Gard/M +Gardener/M +Gardie/M +Gardiner/M +Gardner/M +Gardy/M +Gare/MH +Garek/M +Gareth/M +Garey/M +Garfield/M +Garfunkel/M +Gargantua/M +Garibaldi/M +Garik/M +Garland/M +Garner/M +Garnet/M +Garnett/M +Garnette/M +Garold/M +Garrard/M +Garrek/M +Garret/M +Garreth/M +Garrett/M +Garrick/M +Garrik/M +Garrison/M +Garrot/M +Garrott/M +Garry/M +Garth/M +Garv/M +Garvey/M +Garvin/M +Garvy/M +Garwin/M +Garwood/M +Gary/M +Garza/M +Gascony/M +Gaspar/M +Gaspard/M +Gasparo/M +Gasper/M +Gasser/M +Gasset/M +Gaston/M +Gates +Gatlinburg/M +Gatling/M +Gatorade/M +Gatsby/M +Gatun/M +Gauguin/M +Gaul/MS +Gaulish/M +Gaulle/M +Gaultiero/M +Gauntley/M +Gauss/M +Gaussian +Gautama/M +Gauthier/M +Gautier/M +Gav/MN +Gavan/M +Gaven/M +Gavin/M +Gavra/M +Gavrielle/M +Gawain/M +Gawen/M +Gay/M +Gaye/M +Gayel/M +Gayelord/M +Gayla/M +Gayle/RM +Gayleen/M +Gaylene/M +Gayler/M +Gaylor/M +Gaylord/M +Gaynor/M +Gaza/M +Gaziantep/M +Gd/M +Gdansk/M +Ge/M +Gearalt/M +Gearard/M +Geary/M +Gehenna/M +Gehrig/M +Geiger/M +Geigy/M +Gelya/M +Gemini/SM +Gemma/M +Gen/M +Gena/M +Genaro/M +Gene/M +Genesco/M +Genesis/M +Genet/M +Geneva/M +Genevieve/M +Genevra/M +Genghis/M +Genia/M +Genna/M +Genni/M +Gennie/M +Gennifer/M +Genny/M +Geno/M +Genoa/SM +Genovera/M +Gentile's +Gentry/M +Genvieve/M +Geo/M +Geoff/M +Geoffrey/M +Geoffry/M +Georas/M +Geordie/M +Georg/M +George/SM +Georgeanna/M +Georgeanne/M +Georgena/M +Georgeta/M +Georgetown/M +Georgetta/M +Georgette/M +Georgi/M +Georgia/M +Georgian/S +Georgiana/M +Georgianna/M +Georgianne/M +Georgie/M +Georgina/M +Georgine/M +Georgy/M +Ger/M +Gerald/M +Geralda/M +Geraldine/M +Gerard/M +Gerardo/M +Gerber/M +Gerda/M +Gerek/M +Gerhard/M +Gerhardine/M +Gerhardt/M +Geri/M +Gerianna/M +Gerianne/M +Gerick/M +Gerik/M +Geritol/M +Gerladina/M +Germain/M +Germaine/M +German/SM +Germana/M +Germania/M +Germanic/M +Germantown/M +Germany/M +Germayne/M +Gerome/M +Geronimo/M +Gerrard/M +Gerri/M +Gerrie/M +Gerrilee/M +Gerry/M +Gershwin/MS +Gert/M +Gerta/M +Gerti/M +Gertie/M +Gertrud/M +Gertruda/M +Gertrude/M +Gertrudis/M +Gerty/M +Gery/M +Gestapo/SM +Gethsemane/M +Getty/M +Gettysburg/M +Gewürztraminer +Ghana/M +Ghanaian/MS +Ghanian's +Ghats/M +Ghent/M +Gherardo/M +Ghibelline/M +Giacinta/M +Giacobo/M +Giacometti/M +Giacomo/M +Giacopo/M +Gian/M +Giana/M +Gianina/M +Gianna/M +Gianni/M +Giannini/M +Giauque/M +Giavani/M +Gib/M +Gibb/MS +Gibbie/M +Gibbon/M +Gibby/M +Gibraltar/MS +Gibson/M +Giddings/M +Gide/M +Gideon/MS +Gielgud/M +Gienah/M +Giff/RM +Giffard/M +Giffer/M +Giffie/M +Gifford/M +Giffy/M +Gigi/M +Gil/MY +Gila/M +Gilbert/M +Gilberta/M +Gilberte/M +Gilbertina/M +Gilbertine/M +Gilberto/M +Gilbertson/M +Gilburt/M +Gilchrist/M +Gilda/M +Gilead/M +Gilemette/M +Giles +Gilgamesh/M +Gilkson/M +Gill/M +Gillan/M +Gilles +Gillespie/M +Gillette/M +Gilli/M +Gilliam/M +Gillian/M +Gillie/M +Gilligan/M +Gilly/M +Gilmore/M +Gimbel/M +Gina/M +Ginelle/M +Ginevra/M +Ginger/M +Gingrich/M +Ginni/M +Ginnie/M +Ginnifer/M +Ginny/M +Gino/M +Ginsberg/M +Ginsburg/M +Gioconda/M +Giordano/M +Giorgi/M +Giorgia/M +Giorgio/M +Giorgione/M +Giotto/M +Giovanna/M +Giovanni/M +Gipsy's +Giralda/M +Giraldo/M +Giraud/M +Giraudoux/M +Gisela/M +Giselbert/M +Gisele/M +Gisella/M +Giselle/M +Gish/M +Giuditta/M +Giulia/M +Giuliano/M +Giulietta/M +Giulio/M +Giuseppe/M +Giustina/M +Giustino/M +Giusto/M +Giza/M +Gizela/M +Gk/M +Glad/M +Gladi/M +Gladstone/MS +Gladys +Glaser/M +Glasgow/M +Glass/M +Glastonbury/M +Glaswegian/S +Gleason/M +Gleda/M +Glen/M +Glenda/M +Glendale/M +Glenden/M +Glendon/M +Glenine/M +Glenn/M +Glenna/M +Glennie/M +Glennis/M +Glori/M +Gloria/M +Gloriana/M +Gloriane/M +Glory/M +Gloucester/M +Glover/M +Glyn/M +Glynda/M +Glynis/M +Glynn/M +Glynnis/M +Gnni/M +Gnostic/M +Gnosticism/M +Goa/M +Gobi/M +God/M +Godard/M +Godart/M +Goddard/M +Goddart/M +Godfree/M +Godfrey/M +Godfry/M +Godiva/M +Godot/M +Godspeed/S +Godthaab/M +Godunov/M +Godwin/M +Godzilla/M +Goebbels/M +Goering/M +Goethals/M +Goethe/M +Goff/M +Gog/M +Gogh/M +Gogol/M +Goiania/M +Golan/M +Golconda/M +Golda/M +Goldarina/M +Goldberg/M +Golden/M +Goldi/M +Goldia/M +Goldie/M +Goldilocks/M +Goldina/M +Golding/M +Goldman/M +Goldsmith/M +Goldstein/M +Goldwater/M +Goldwyn/M +Goldy/M +Goleta/M +Golgotha/M +Goliath/M +Goliaths +Gomez/M +Gomorrah/M +Gompers/M +Gondwanaland/M +Gonzales/M +Gonzalez/M +Gonzalo/M +Goober/M +Good/M +Goodman/M +Goodrich/M +Goodwin/M +Goodyear/M +Google/M +Gopher +Goran/M +Goraud/M +Gorbachev +Gordan/M +Gorden/M +Gordian/M +Gordie/M +Gordimer/M +Gordon/M +Gordy/M +Gore/M +Goren/M +Gorey/M +Gorgas +Gorgon/M +Gorgonzola/M +Gorham/M +Gorky/M +Gospel/SM +Goth/M +Gotham/M +Gothart/M +Gothic/S +Gothicism/M +Goths +Gottfried/M +Goucher/M +Gouda/SM +Gould/M +Gounod/M +Governor +Goya/M +Gr/M +Gracchus/M +Grace/M +Graceland/M +Gracia/M +Gracie/M +Graciela/M +Gradeigh/M +Gradey/M +Grady/M +Graehme/M +Graeme/M +Graff/M +Graffias/M +Grafton/M +Graham/M +Grahame/M +Graig/M +Grail/SM +Gram/M +Grammy/S +Grampians +Gran/M +Granada/M +Grange/MR +Granger/M +Grannie/M +Granny/M +Grant/M +Grantham/M +Granthem/M +Grantley/M +Granville/M +Grass/M +Grata/M +Gratia/M +Gratiana/M +Graves/M +Gray/M +Grayce/M +Grayson/M +Grazia/M +Grecian/S +Greece/M +Greek/SM +Greeley/M +Green/M +Greenberg/M +Greenblatt/M +Greenbriar/M +Greene/M +Greenfeld/M +Greenfield/M +Greenland/M +Greenpeace/M +Greensboro/M +Greensleeves/M +Greensville/M +Greentree/M +Greenville/M +Greenwich/M +Greer/M +Greg/M +Gregg/M +Greggory/M +Gregoire/M +Gregoor/M +Gregor/M +Gregorian +Gregorio/M +Gregorius/M +Gregory/M +Grenada/M +Grenadian/S +Grenadines +Grendel/M +Grenier/M +Grenoble/M +Grenville/M +Gresham/M +Greta/M +Gretal/M +Gretchen/M +Grete/M +Gretel/M +Grethel/M +Gretna/M +Gretta/M +Gretzky/M +Grey/M +Grieg/M +Grier/M +Griff/M +Griffie/M +Griffin/M +Griffith/M +Griffy/M +Grimaldi/M +Grimes +Grimm/M +Grinch/M +Gris/M +Griselda/M +Grissel/M +Griswold/M +Griz/M +Gromyko/M +Groot/M +Gropius/M +Gross +Grosset/M +Grossman/M +Grosvenor/M +Grosz/M +Grotius/M +Groton/M +Grove/RM +Grover/M +Grumman/M +Grundy/M +Grus/M +Grusky/M +Gruyeres +Gruyère +Grünewald/M +Guadalajara/M +Guadalcanal/M +Guadalquivir/M +Guadalupe/M +Guadeloupe/M +Guallatiri/M +Gualterio/M +Guam/M +Guamanian/SM +Guangzhou +Guantanamo/M +Guarani/M +Guardia/M +Guarnieri/M +Guatemala/M +Guatemalan/S +Guayaquil/M +Gucci/M +Guelph/M +Guendolen/M +Guenevere/M +Guenna/M +Guenther/M +Guernsey/SM +Guerra/M +Guerrero/M +Guevara/M +Guggenheim/M +Guglielma/M +Guglielmo/M +Guhleman/M +Gui/M +Guiana/M +Guido/M +Guilbert/M +Guillaume/M +Guillema/M +Guillemette/M +Guillermo/M +Guinea/M +Guinean/S +Guinevere/M +Guinna/M +Guinness/M +Guiyang +Guizot/M +Gujarat/M +Gujarati/M +Gujranwala/M +Gullah/M +Gulliver/M +Gun/M +Gunar/M +Gunderson/M +Gunilla/M +Gunnar/M +Gunner/M +Guntar/M +Gunter/M +Gunther/M +Guofeng/M +Gupta/M +Gurkha/M +Gus/M +Gusella/M +Guss +Gussi/M +Gussie/M +Gussy/M +Gusta/M +Gustaf/M +Gustafson/M +Gustav/M +Gustave/M +Gustavo/M +Gustavus/M +Gusti/M +Gustie/M +Gusty/M +Gutenberg/M +Guthrey/M +Guthrie/M +Guthry/M +Gutierrez/M +Guy/M +Guyana/M +Guyanese +Guzman/M +Gwalior/M +Gwen/M +Gwendolen/M +Gwendolin/M +Gwendoline/M +Gwendolyn/M +Gweneth/M +Gwenette/M +Gwenneth/M +Gwenni/M +Gwennie/M +Gwenny/M +Gwenora/M +Gwenore/M +Gwyn/M +Gwyneth/M +Gwynne/M +Gypsy/SM +Gödel/M +Göteborg/M +H +H's +HBO/M +HDTV +HF +HHS +HI +HIV +HM +HMO +HMS +HOV +HP +HQ +HR +HRH +HS +HST +HTML +HTTP +HUD +Ha/M +Haag/M +Haas/M +Habakkuk/M +Haber/M +Haberman/M +Habib/M +Hackett/M +Had/M +Hadamard/M +Hadar/M +Haddad/M +Hades +Hadlee/M +Hadleigh/M +Hadley/M +Hadria/M +Hadrian/M +Hafiz/M +Hagan/M +Hagar/M +Hagen/M +Hager/M +Haggai/M +Hagiographa/M +Hagstrom/M +Hague/M +Hahn/M +Haifa/M +Hailee/M +Hailey/M +Haily/M +Haiphong/M +Haiti/M +Haitian/S +Hakeem/M +Hakim/M +Hakka/M +Hakluyt/M +Hal/SMY +Haldane/M +Hale/M +Haleakala/M +Haleigh/M +Halette/M +Haley/M +Hali/M +Halie/M +Halifax/M +Halimeda/M +Hall/M +Halley/M +Halli/M +Hallie/M +Hallinan/M +Hallmark/M +Halloween/MS +Hallsy/M +Hally/M +Halpern/M +Halsey/M +Halsy/M +Ham/M +Hamal/M +Haman/M +Hamburg/MS +Hamel/M +Hamey/M +Hamhung/M +Hamid/M +Hamil/M +Hamilcar/M +Hamilton/M +Hamiltonian/MS +Hamish/M +Hamitic/M +Hamlen/M +Hamlet/M +Hamlin/M +Hammad/M +Hammarskjold/M +Hammerstein/M +Hammett/M +Hammond/M +Hammurabi/M +Hamnet/M +Hampshire/M +Hampton/M +Hamsun/M +Han/SM +Hana/M +Hanan/M +Hancock/M +Handel/M +Handy/M +Haney/M +Hangul/M +Hangzhou +Hank/M +Hankel/M +Hanna/M +Hannah/M +Hanni/MS +Hannibal/M +Hannie/M +Hanny/M +Hanoi/M +Hanover/M +Hanoverian +Hans/N +Hansel/M +Hansen/M +Hansiain/M +Hanson/M +Hanuka/S +Hanukkah/M +Hanukkahs +Hapgood/M +Happy/M +Hapsburg/M +Harald/M +Harare +Harbert/M +Harbin/M +Harcourt/M +Hardin/M +Harding/M +Hardy/M +Hargreaves/M +Harlan/M +Harland/M +Harlem/M +Harlen/M +Harlene/M +Harlequin +Harley/M +Harli/M +Harlie/M +Harlin/M +Harlow/M +Harman/M +Harmon/M +Harmonia/M +Harmonie/M +Harmony/M +Harold/M +Haroun/M +Harp/MR +Harper/M +Harpy/SM +Harrell/M +Harri/SM +Harrie/M +Harriet/M +Harriett/M +Harrietta/M +Harriette/M +Harrington/M +Harriot/M +Harriott/M +Harrisburg/M +Harrison/M +Harrisonburg/M +Harry/M +Hart/M +Harte/M +Hartford/M +Hartley/M +Hartline/M +Hartman/M +Hartwell/M +Harv/M +Harvard/M +Harvey/MS +Harwell/M +Harwilll/M +Hasbro/M +Hasheem/M +Hashim/M +Hasidim +Haskel/M +Haskell/M +Haskins/M +Haslett/M +Hastie/M +Hastings/M +Hasty/M +Hatchure/M +Hatfield/M +Hathaway/M +Hatteras/M +Hatti/M +Hattie/M +Hatty/M +Haugen/M +Hauptmann/M +Hausa/M +Hausdorff/M +Hauser/M +Havana/SM +Havarti +Havel/M +Haven/M +Haw +Hawaii/M +Hawaiian/S +Hawking +Hawkins/M +Hawley/M +Hawthorne/M +Hay/SM +Hayden/M +Haydn/M +Haydon/M +Hayes +Hayley/M +Haynes +Hayward/M +Haywood/M +Hayyim/M +Haze/M +Hazel/M +Hazlett/M +Hazlitt/M +He/M +Head/M +Heall/M +Hearst/M +Heartwood/M +Heath/MR +Heather/M +Heathkit/M +Heathman/M +Heaviside/M +Heb/M +Hebe/M +Hebert/M +Hebraic +Hebraism/MS +Hebrew/SM +Hebrides/M +Hecate/M +Hector/M +Hecuba/M +Heda/M +Hedda/M +Heddi/M +Heddie/M +Hedi/M +Hedvig/M +Hedvige/M +Hedwig/M +Hedwiga/M +Hedy/M +Heep/M +Hefner/M +Hegel/M +Hegelian +Hegira/M +Heida/M +Heidegger/M +Heidelberg/M +Heidi/M +Heidie/M +Heifetz/M +Heimlich/M +Heindrick/M +Heine/M +Heineken/M +Heinlein/M +Heinrich/M +Heinrick/M +Heinrik/M +Heinz/M +Heinze/M +Heisenberg/M +Heiser/M +Hejira's +Helaina/M +Helaine/M +Helen/M +Helena/M +Helene/M +Helenka/M +Helga/M +Helge/M +Helicon/M +Heliopolis/M +Helios/M +Hell's +Hellene/SM +Hellenic +Hellenism/MS +Hellenist/MS +Hellenistic +Hellenization/M +Hellenize +Heller/M +Hellespont/M +Helli/M +Hellman/M +Helmholtz/M +Helmut/M +Helsa/M +Helsinki/M +Helvetian/S +Helvetius/M +Helyn/M +Hemingway/M +Hench/M +Henderson/M +Hendrick/SM +Hendrickson/M +Hendrik/M +Hendrika/M +Hendrix/M +Henka/M +Henley/M +Hennessey/M +Henri/M +Henrie/M +Henrieta/M +Henrietta/M +Henriette/M +Henrik/M +Henry/M +Henryetta/M +Hensley/M +Henson/M +Hepburn/M +Hephaestus/M +Hephzibah/M +Hepplewhite +Hera/M +Heracles/M +Heraclitus/M +Herb/M +Herbart/M +Herbert/M +Herbie/M +Herby/M +Herc/M +Herculaneum/M +Hercule/MS +Herculean +Herculie/M +Herder/M +Hereford/SM +Heriberto/M +Herkimer/M +Herman/M +Hermann/M +Hermaphroditus/M +Hermes +Hermia/M +Hermie/M +Hermina/M +Hermine/M +Herminia/M +Hermione/M +Hermite/M +Hermon/M +Hermosa/M +Hermosillo/M +Hermy/M +Hernandez/M +Hernando/M +Herod/M +Herodotus/M +Herold/M +Herr/MG +Herrera/M +Herrick/M +Herring/M +Herrington/M +Hersch/M +Herschel/M +Hersey/M +Hersh/M +Hershel/M +Hershey/M +Herta/M +Hertha/M +Hertz/M +Hertzog/M +Hertzsprung/M +Herve/M +Hervey/M +Herzegovina/M +Herzl/M +Hesiod/M +Hesperus/M +Hess/M +Hesse/M +Hessian/MS +Hester/M +Hesther/M +Hestia/M +Heston/M +Hetti/M +Hettie/M +Hetty/M +Heublein/M +Heusen/M +Heuser/M +Hew/M +Hewe/M +Hewet/M +Hewett/M +Hewie/M +Hewitt/M +Hewlett/M +Heyerdahl/M +Heywood/M +Hezekiah/M +Hf/M +Hg/M +Hi/M +Hialeah/M +Hiawatha/M +Hibernia/M +Hibernian/S +Hickey/SM +Hickman/M +Hickok/M +Hicks/M +Hieronymus/M +Higashiosaka +Higgins/M +Highfield/M +Highlander/SM +Highlands +Highness/M +Hilario/M +Hilarius/M +Hilary/M +Hilbert/M +Hilda/M +Hildagard/M +Hildagarde/M +Hilde/M +Hildebrand/M +Hildegaard/M +Hildegarde/M +Hildy/M +Hill/M +Hillard/M +Hillary/M +Hillcrest/M +Hillel/M +Hillery/M +Hilliard/M +Hilliary/M +Hillie/M +Hillier/M +Hillsboro/M +Hillsdale/M +Hilly/RM +Hillyer/M +Hilton/M +Himalaya/MS +Himalayan/S +Himmler/M +Hinayana/M +Hinda/M +Hindemith/M +Hindenburg/M +Hindi/M +Hindu/MS +Hinduism/SM +Hindustan/M +Hindustani/MS +Hines/M +Hinkle/M +Hinsdale/M +Hinton/M +Hinze/M +Hipparchus/M +Hippocrates/M +Hippocratic +Hiram/M +Hirey/M +Hirohito/M +Hiroshi/M +Hiroshima/M +Hirsch/M +Hispanic/SM +Hispaniola/M +Hiss/M +Hitachi/M +Hitchcock/M +Hitler/SM +Hittite/SM +Hmong +Ho/M +Hobard/M +Hobart/M +Hobbes/M +Hobbs/M +Hobday/M +Hobey/M +Hobie/M +Hoboken/M +Hockney/M +Hodge/MS +Hodgkin/M +Hoebart/M +Hoff/M +Hoffa/M +Hoffman/M +Hofstadter/M +Hogan/M +Hogarth/M +Hohenlohe/M +Hohenstaufen/M +Hohenzollern/M +Hohhot/M +Hokkaido/M +Hokusai/M +Holbein/M +Holbrook/M +Holcomb/M +Holden/M +Holder/M +Holiday/M +Holiness/MS +Holland/RMSZ +Hollandaise/M +Hollander/M +Hollerith/M +Holley/M +Holli/SM +Hollie/M +Hollister/M +Holloway/M +Holly/M +Hollyanne/M +Hollywood/M +Holm/M +Holman/M +Holmes +Holocaust +Holocene +Holst/M +Holstein/MS +Holt/M +Holyoke/M +Holzman/M +Hom/MR +Homer/M +Homere/M +Homeric +Homerus/M +Hon/M +Honda/M +Hondo/M +Honduran/S +Honduras/M +Honecker/M +Honey/M +Honeywell/M +Honiara/M +Honolulu/M +Honor/M +Honoria/M +Honshu/M +Hood/M +Hooke/MR +Hooker/M +Hooper/M +Hoosier/SM +Hoover/MS +Hope/M +Hopewell/M +Hopi/SM +Hopkins/M +Hopkinsian/M +Hopper/M +Horace/M +Horacio/M +Horatia/M +Horatio/M +Horatius/M +Hormel/M +Hormuz/M +Horn/M +Hornblower/M +Horne/M +Horowitz/M +Horst/M +Hort/MN +Horten/M +Hortense/M +Hortensia/M +Horton/M +Horus/M +Hosea/M +Host/MS +Hottentot/SM +Houdaille/M +Houdini/M +House/M +Housman/M +Houston/M +Houyhnhnm/M +Howard/M +Howe/M +Howell/MS +Howey/M +Howie/M +Howrah/M +Hoyle/SM +Hoyt/M +Hrothgar/M +Hts/M +Huang/M +Hubbard/M +Hubble/M +Hube/RM +Huber/M +Hubert/M +Huberto/M +Hubey/M +Hubie/M +Huck/M +Huddersfield/M +Hudson/M +Huerta/M +Huey/M +Huff/M +Huffman/M +Huggins +Hugh/MS +Hughie/M +Hugibert/M +Hugo/M +Huguenot/SM +Hugues/M +Hui/M +Huitzilopitchli/M +Hulda/M +Hull/M +Humbert/M +Humberto/M +Humboldt/M +Hume/M +Humfrey/M +Humfrid/M +Humfried/M +Hummel/M +Humphrey/SM +Humpty/M +Humvee +Hun/MS +Hunfredo/M +Hung/M +Hungarian/MS +Hungary/M +Hunt/MR +Hunter/M +Huntington/M +Huntlee/M +Huntley/M +Huntsville/M +Hurlee/M +Hurleigh/M +Hurley/M +Huron/SM +Hurst/M +Hurwitz/M +Hus +Husain's +Husein/M +Hussein/M +Husserl/M +Huston/M +Hutchins/M +Hutchinson/M +Hutchison/M +Hutton/M +Hutu/M +Huxley/M +Huygens/M +Hy/M +Hyacinth/M +Hyacintha/M +Hyacinthe/M +Hyacinthia/M +Hyacinthie/M +Hyades +Hyannis/M +Hyatt/M +Hyde/M +Hyderabad/M +Hydra/M +Hyman/M +Hymen/M +Hymie/M +Hynda/M +Hyperion/M +Hyundai/M +Hz +Héloise/M +I +I'd +I'll +I'm +I've +IA +IBM/M +ICBM/S +ICC +ICU +ID +IDs +IE +IEEE +IL +IMF +IMHO +IMNSHO +IMO +IN +INRI +INS +INTERNET/M +IOU +IPA +IQ +IRA/S +IRS +ISBN +ISO +IT +ITCorp/M +ITT +ITcorp/M +IUD/S +IV +IVs +Ia/M +Iaccoca/M +Iago/M +Iain/M +Ian/M +Ianthe/M +Ibadan/M +Ibbie/M +Ibby/M +Iberia/M +Iberian/MS +Ibero/M +Ibo/M +Ibrahim/M +Ibsen/M +Icarus/M +Ice/M +Iceland/MRZ +Icelander/M +Icelandic +Ichabod/M +Ida/M +Idaho/MS +Idahoan/S +Idahoes +Idalia/M +Idalina/M +Idaline/M +Idell/M +Idelle/M +Idette/M +Ieyasu/M +Ifni/M +Iggie/M +Iggy/M +Ignace/M +Ignacio/M +Ignacius/M +Ignatius/M +Ignaz/M +Ignazio/M +Igor/M +Iguassu/M +Ijsselmeer/M +Ike/M +Ikey/M +Ikhnaton/M +Ila/M +Ilaire/M +Ilario/M +Ileana/M +Ileane/M +Ilene/M +Iliad/MS +Ilise/M +Ilka/M +Ill/M +Illa/M +Illinois/M +Illinoisan/MS +Illuminati +Ilona/M +Ilsa/M +Ilse/M +Ilysa/M +Ilyse/M +Ilyssa/M +Ilyushin/M +Imagen/M +Imbrium/M +Imelda/M +Immanuel/M +Imogen/M +Imogene/M +Imojean/M +Imus/M +In/PM +Ina/M +Inc/M +Inca/SM +Inchon/M +Ind/M +Independence/M +India/M +Indian/SM +Indiana/M +Indianan/S +Indianapolis/M +Indianian/S +Indira/M +Indochina/M +Indochinese +Indonesia/M +Indonesian/S +Indore/M +Indra/M +Indus/M +Indy/SM +Ines +Inesita/M +Inessa/M +Inez/M +Informatica/M +Inga/M +Ingaberg/M +Ingaborg/M +Ingamar/M +Ingar/M +Inge/RM +Ingeberg/M +Ingeborg/M +Ingelbert/M +Ingemar/M +Inger/M +Ingersoll/M +Inglebert/M +Inglewood/M +Inglis/M +Ingmar/M +Ingra/M +Ingram/M +Ingres/M +Ingrid/M +Ingrim/M +Ingunna/M +Inigo/M +Inna/M +Innis/M +Innocent/M +Innsbruck/M +Inonu/M +Inquisition/MS +Inst +Intel/M +Intelsat/M +Interdata/M +Internationale/M +Internet/M +Interpol/M +Inuit/MS +Inverness/M +Io/M +Iolande/M +Iolanthe/M +Iona/M +Ionesco/M +Ionian/M +Ionic/S +Iorgo/MS +Iormina/M +Iosep/M +Iowa/SM +Iowan/S +Iphigenia/M +Ipswich/M +Iqbal/M +Iquitos/M +Ir/M +Ira/M +Iran/M +Iranian/MS +Iraq/M +Iraqi/SM +Ireland/M +Irena/M +Irene/M +Irina/M +Iris +Irish/R +Irishman/M +Irishmen +Irishwoman/M +Irishwomen +Irita/M +Irkutsk/M +Irma/M +Iroquoian/MS +Iroquois/M +Irrawaddy/M +Irtish/M +Irv/MG +Irvin/M +Irvine/M +Irving/M +Irwin/M +Irwinn/M +Isa/M +Isaac/SM +Isaak/M +Isabel/M +Isabelita/M +Isabella/M +Isabelle/M +Isac/M +Isacco/M +Isador/M +Isadora/M +Isadore/M +Isahella/M +Isaiah/M +Isak/M +Iscariot/M +Iseabal/M +Isfahan/M +Isherwood/M +Ishim/M +Ishmael/M +Ishtar/M +Isiah/M +Isiahi/M +Isidor/M +Isidora/M +Isidore/M +Isidoro/M +Isidro/M +Isis/M +Islam/SM +Islamabad/M +Islamic/S +Islandia/M +Ismael/M +Isobel/M +Isolde/M +Ispahan's +Ispell/M +Israel/MS +Israeli/MS +Israelite/SM +Issac/M +Issi/M +Issiah/M +Issie/M +Issy/M +Istanbul/M +Istvan/M +Isuzu/M +It +Itaipu/M +Ital/M +Italian/MS +Italianate/GSD +Italy/M +Itasca/M +Itch/M +Itel/M +Ithaca/M +Ithacan +Ito/M +Iva/M +Ivan/M +Ivanhoe/M +Ivar/M +Ive/MRS +Iver/M +Ivett/M +Ivette/M +Ivie/M +Ivonne/M +Ivor/M +Ivory/M +Ivy/M +Izaak/M +Izabel/M +Izak/M +Izanagi/M +Izanami/M +Izhevsk/M +Izmir/M +Izvestia/M +Izzy/M +J's +J/X +JCS +JD +JFK/M +JP +JV +Jabez/M +Jablonsky/M +Jacenta/M +Jacinda/M +Jacinta/M +Jacintha/M +Jacinthe/M +Jack/M +Jackelyn/M +Jacki/M +Jackie/M +Jacklin/M +Jacklyn/M +Jackman/M +Jackquelin/M +Jackqueline/M +Jackson/SM +Jacksonian +Jacksonville/M +Jacky/M +Jaclin/M +Jaclyn/M +Jacob/SM +Jacobean +Jacobi/M +Jacobian/M +Jacobin/M +Jacobite/M +Jacobo/M +Jacobs/N +Jacobsen/M +Jacobson/M +Jacobus +Jacoby/M +Jacquard/SM +Jacquelin/M +Jacqueline/M +Jacquelyn/M +Jacquelynn/M +Jacquenetta/M +Jacquenette/M +Jacques/M +Jacquetta/M +Jacquette/M +Jacqui/M +Jacquie/M +Jacuzzi/S +Jacynth/M +Jada/M +Jade/M +Jae/M +Jaeger/M +Jagger/M +Jaime/M +Jaimie/M +Jain/M +Jaine/M +Jainism/M +Jaipur/M +Jakarta/M +Jake/MS +Jakie/M +Jakob/M +Jamaal/M +Jamaica/M +Jamaican/S +Jamal/M +Jamar/M +Jame/MS +Jamel/M +Jameson/M +Jamestown/M +Jamesy/M +Jamey/M +Jami/M +Jamie/M +Jamil/M +Jamill/M +Jamima/M +Jamison/M +Jammal/M +Jammie/M +Jan/M +Jana/M +Janacek/M +Janaya/M +Janaye/M +Jandy/M +Jane/M +Janean/M +Janeczka/M +Janeen/M +Janeiro/M +Janek/M +Janel/M +Janela/M +Janell/M +Janella/M +Janelle/M +Janene/M +Janenna/M +Janessa/M +Janesville/M +Janet/M +Janeta/M +Janetta/M +Janette/M +Janeva/M +Janey/M +Jania/M +Janice/M +Janie/M +Janifer/M +Janina/M +Janine/M +Janis/M +Janith/M +Janka/M +Janna/M +Jannel/M +Jannelle/M +Jannie/M +Janos/M +Janot/M +Jansen/M +Jansenist/M +January/MS +Janus/M +Jany/M +Japan/M +Japanese/SM +Japura/M +Jaquelin/M +Jaquelyn/M +Jaquenetta/M +Jaquenette/M +Jaquith/M +Jarad/M +Jard/M +Jareb/M +Jared/M +Jarib/M +Jarid/M +Jarlsberg +Jarrad/M +Jarred/M +Jarret/M +Jarrett/M +Jarrid/M +Jarrod/M +Jarvis/M +Jase/M +Jasen/M +Jasmin/M +Jasmina/M +Jasmine/M +Jason/M +Jasper/M +Jastrow/M +Jasun/M +Java/SM +JavaScript/M +Javanese +Javier/M +Jaxartes/M +Jay/M +Jayapura/M +Jaycee/SM +Jaye/M +Jayme/M +Jaymee/M +Jaymie/M +Jayne/M +Jaynell/M +Jayson/M +Jazmin/M +Jdavie/M +Jean/M +Jeana/M +Jeane/M +Jeanelle/M +Jeanette/M +Jeanie/M +Jeanine/M +Jeanna/M +Jeanne/M +Jeannette/M +Jeannie/M +Jeannine/M +Jecho/M +Jed/M +Jedd/M +Jeddy/M +Jedediah/M +Jedi/M +Jedidiah/M +Jeep/S +Jeeves/M +Jeff/M +Jefferey/M +Jefferson/M +Jeffersonian/S +Jeffery/M +Jeffie/M +Jeffrey/SM +Jeffry/M +Jeffy/M +Jehanna/M +Jehoshaphat/M +Jehovah/M +Jehu/M +Jekyll/M +Jelene/M +Jello/M +Jemie/M +Jemima/M +Jemimah/M +Jemmie/M +Jemmy/M +Jen/M +Jena/M +Jenda/M +Jenelle/M +Jeni/M +Jenica/M +Jeniece/M +Jenifer/M +Jeniffer/M +Jenilee/M +Jenine/M +Jenkins/M +Jenn/RMJ +Jenna/M +Jennee/M +Jenner/M +Jennette/M +Jenni/M +Jennica/M +Jennie/M +Jennifer/M +Jennilee/M +Jennine/M +Jennings/M +Jenny/M +Jeno/M +Jens/N +Jensen/M +Jephthah/M +Jerad/M +Jerald/M +Jeralee/M +Jeramey/M +Jeramie/M +Jere/M +Jereme/M +Jeremiah/M +Jeremiahs +Jeremias/M +Jeremie/M +Jeremy/M +Jeri/M +Jericho/M +Jermain/M +Jermaine/M +Jermayne/M +Jeroboam/M +Jerold/M +Jerome/M +Jeromy/M +Jerri/M +Jerrie/M +Jerrilee/M +Jerrilyn/M +Jerrine/M +Jerrod/M +Jerrold/M +Jerrome/M +Jerry/M +Jerrylee/M +Jersey/MS +Jerusalem/M +Jervis/M +Jes +Jess/M +Jessa/M +Jessalin/M +Jessalyn/M +Jessamine/M +Jessamyn/M +Jesse/M +Jessee/M +Jesselyn/M +Jessey/M +Jessi/M +Jessica/M +Jessie/M +Jessika/M +Jessy/M +Jesuit/SM +Jesus +Jeth/M +Jethro/M +Jew/MS +Jewel/M +Jewell/MD +Jewelle/M +Jewelled/M +Jewess/SM +Jewish/P +Jewishness/MS +Jewry/MS +Jezebel/MS +Jidda/M +Jilin +Jill/M +Jillana/M +Jillane/M +Jillayne/M +Jilleen/M +Jillene/M +Jilli/M +Jillian/M +Jillie/M +Jilly/M +Jim/M +Jimenez/M +Jimmie/M +Jimmy/M +Jinan +Jinnah/M +Jinny/M +Jivaro/M +Jo/MY +Joachim/M +Joan/M +Joana/M +Joane/M +Joanie/M +Joann/M +Joanna/M +Joanne/SM +Joaquin/M +Job/SM +Jobey/M +Jobi/M +Jobie/M +Jobina/M +Jobrel/M +Joby/M +Jobye/M +Jobyna/M +Jocasta/M +Jocelin/M +Joceline/M +Jocelyn/M +Jocelyne/M +Jock/M +Jocko/M +Jodee/M +Jodi/M +Jodie/M +Jody/M +Joe/M +Joeann/M +Joel/MY +Joela/M +Joelie/M +Joell/MN +Joella/M +Joelle/M +Joellen/M +Joelly/M +Joellyn/M +Joelynn/M +Joesph/M +Joete/M +Joey/M +Jogjakarta/M +Johan/M +Johann/M +Johanna/M +Johannah/M +Johannes +Johannesburg/M +Johansen/M +Johanson/M +John/SM +Johna/MH +Johnath/M +Johnathan/M +Johnathon/M +Johnette/M +Johnie/M +Johnna/M +Johnnie/M +Johnny/M +Johns/N +Johnsen/M +Johnson/M +Johnston/M +Johnstown/M +Johny/M +Joice/M +Jojo/M +Jolee/M +Joleen/M +Jolene/M +Joletta/M +Joli/M +Jolie/M +Joliet's +Joline/M +Jolla/M +Jolson/M +Joly/M +Jolyn/M +Jolynn/M +Jon/M +Jonah/M +Jonahs +Jonas +Jonathan/M +Jonathon/M +Jone/MS +Jonell/M +Jones/S +Joni/MS +Jonie/M +Jonson/M +Joplin/M +Jordain/M +Jordan/M +Jordana/M +Jordanian/S +Jordanna/M +Jordon/M +Jorey/M +Jorgan/M +Jorge/M +Jorgensen/M +Jorgenson/M +Jori/M +Jorie/M +Jorrie/M +Jorry/M +Jory/M +Joscelin/M +Jose/M +Josee/M +Josef/M +Josefa/M +Josefina/M +Joseito/M +Joseph/M +Josepha/M +Josephina/M +Josephine/M +Josephs +Josephson/M +Josephus/M +Josey/M +Josh/M +Joshia/M +Joshua/M +Joshuah/M +Josi/M +Josiah/M +Josias/M +Josie/M +Josselyn/M +Josue/M +Josy/M +Joule/M +Jourdain/M +Jourdan/M +Jovanovich/M +Jove/M +Jovian +Joy/M +Joya/M +Joyan/M +Joyann/M +Joyce/M +Joycean +Joycelin/M +Joye/M +Joyner/M +Jozef/M +Jpn +Jr/M +Jsandye/M +Juan/M +Juana/M +Juanita/M +Juarez +Jubal/M +Jud/M +Judah/M +Judaic +Judaical +Judaism/SM +Judas/S +Judd/M +Jude/M +Judea/M +Judi/MH +Judie/M +Judith/M +Juditha/M +Judon/M +Judson/M +Judy/M +Judye/M +Juggernaut/M +Juieta/M +Jul/M +Jule/MS +Julee/M +Juli/M +Julia/M +Julian/M +Juliana/M +Juliane/M +Juliann/M +Julianna/M +Julianne/M +Julie/M +Julienne/M +Juliet/M +Julieta/M +Julietta/M +Juliette/M +Julina/M +Juline/M +Julio/M +Julissa/M +Julita/M +Julius/M +July/SM +Julys +Jun/M +June/MS +Juneau/M +Junette/M +Jung/M +Jungfrau/M +Jungian +Junia/M +Junie/M +Junina/M +Junior/S +Junker/SM +Juno/M +Jupiter/M +Jurassic +Jurua/M +Justen/M +Justice/M +Justin/M +Justina/M +Justine/M +Justinian/M +Justinn/M +Justino/M +Justis/M +Justus/M +Jutish +Jutland/M +Juvenal/M +Jyoti/M +K's +K/G +KB +KC +KDE/M +KGB +KIA +KKK +KO +KP +KS +KY +Kaaba/M +Kabuki +Kabul/M +Kacey/M +Kacie/M +Kacy/M +Kaddish/M +Kaela/M +Kafka/M +Kafkaesque +Kagoshima/M +Kahaleel/M +Kahlil/M +Kahlua/M +Kahn/M +Kai/M +Kaia/M +Kaifeng/M +Kaila/M +Kaile/M +Kailey/M +Kain/M +Kaine/M +Kaiser/SM +Kaitlin/M +Kaitlyn/M +Kaitlynn/M +Kaja/M +Kajar/M +Kakalina/M +Kala/M +Kalahari/M +Kalamazoo/M +Kalashnikov/M +Kalb/M +Kale/M +Kaleb/M +Kaleena/M +Kalgoorlie/M +Kali/M +Kalie/M +Kalil/M +Kalila/M +Kalina/M +Kalinda/M +Kalindi/M +Kalle/M +Kalli/M +Kally/M +Kalmyk +Kalvin/M +Kama/M +Kamchatka/M +Kamehameha/M +Kameko/M +Kamikaze/MS +Kamila/M +Kamilah/M +Kamillah/M +Kampala/M +Kampuchea/M +Kan/MS +Kanchenjunga/M +Kandace/M +Kandahar/M +Kandinsky/M +Kandy/M +Kane/M +Kania/M +Kankakee/M +Kannada/M +Kano/M +Kanpur/M +Kansan/S +Kansas +Kant/M +Kantian +Kanya/M +Kaohsiung/M +Kaplan/M +Kaposi/M +Kara/M +Karachi/M +Karaganda/M +Karakorum/M +Karalee/M +Karalynn/M +Karamazov/M +Kare/M +Karee/M +Kareem/M +Karel/M +Karen/M +Karena/M +Karenina/M +Kari/M +Karia/M +Karie/M +Karil/M +Karilynn/M +Karim/M +Karin/M +Karina/M +Karine/M +Kariotta/M +Karisa/M +Karissa/M +Karita/M +Karl/MNX +Karla/M +Karlan/M +Karlee/M +Karleen/M +Karlen/M +Karlene/M +Karlie/M +Karlik/M +Karlis +Karloff/M +Karlotta/M +Karlotte/M +Karly/M +Karlyn/M +Karmen/M +Karna/M +Karney/M +Karo/YM +Karol/M +Karola/M +Karole/M +Karolina/M +Karoline/M +Karoly/M +Karon/M +Karp/M +Karrah/M +Karrie/M +Karroo/M +Karry/M +Kary/M +Karyl/M +Karylin/M +Karyn/M +Kasai/M +Kasey/M +Kashmir/SM +Kaspar/M +Kasparov/M +Kasper/M +Kass +Kassandra/M +Kassey/M +Kassi/M +Kassia/M +Kassie/M +Kat/M +Kata/M +Katalin/M +Kate/M +Katee/M +Katelyn/M +Katerina/M +Katerine/M +Katey/M +Kath/M +Katha/M +Katharina/M +Katharine/M +Katharyn/M +Kathe/M +Katherina/M +Katherine/M +Katheryn/M +Kathi/M +Kathiawar/M +Kathie/M +Kathleen/M +Kathlin/M +Kathmandu +Kathrine/M +Kathryn/M +Kathryne/M +Kathy/M +Kathye/M +Kati/M +Katie/M +Katina/M +Katine/M +Katinka/M +Katleen/M +Katlin/M +Katmai/M +Katmandu's +Katowice/M +Katrina/M +Katrine/M +Katrinka/M +Katti/M +Kattie/M +Katuscha/M +Katusha/M +Katy/M +Katya/M +Katz/M +Kauai/M +Kauffman/M +Kaufman/M +Kaunas/M +Kaunda/M +Kawabata/M +Kawasaki/M +Kay/M +Kaycee/M +Kaye/M +Kayla/M +Kayle/M +Kaylee/M +Kayley/M +Kaylil/M +Kaylyn/M +Kayne/M +Kazakh/M +Kazakhstan +Kazan/M +Kazantzakis/M +Kb +Kean/M +Keane/M +Kearney/M +Keary/M +Keaton/M +Keats/M +Keck/M +Keefe/MR +Keefer/M +Keegan/M +Keelby/M +Keeley/M +Keelia/M +Keely/M +Keen/M +Keenan/M +Keene/M +Keewatin/M +Keillor/M +Keir/M +Keisha/M +Keith/M +Kelbee/M +Kelby/M +Kelcey/M +Kelci/M +Kelcie/M +Kelcy/M +Kele/M +Kelila/M +Kellby/M +Kellen/M +Keller/M +Kelley/M +Kelli/M +Kellia/M +Kellie/M +Kellina/M +Kellogg/M +Kellsie/M +Kelly/M +Kellyann/M +Kelsey/M +Kelsi/M +Kelsy/M +Kelt's +Kelvin/M +Kelwin/M +Kemerovo/M +Kemp/M +Kempis/M +Ken/M +Kendal/M +Kendall/M +Kendell/M +Kendra/M +Kendre/M +Kendrick/MS +Kenilworth/M +Kenmore/M +Kenn/M +Kenna/M +Kennan/M +Kennecott/M +Kennedy/M +Kenneth/M +Kennett/M +Kennie/M +Kennith/M +Kenny/M +Kenon/M +Kenosha/M +Kensington/M +Kent/M +Kenton/M +Kentuckian/S +Kentucky/M +Kenya/M +Kenyan/S +Kenyatta/M +Kenyon/M +Keogh/M +Keokuk/M +Kepler/M +Ker/M +Kerby/M +Kerensky/M +Keri/M +Keriann/M +Kerianne/M +Kerk/M +Kermie/M +Kermit/M +Kermy/M +Kern/M +Kerouac/M +Kerr/M +Kerri/M +Kerrie/M +Kerrill/M +Kerrin/M +Kerry/M +Kerstin/M +Kerwin/M +Kerwinn/M +Kesley/M +Keslie/M +Kessia/M +Kessiah/M +Kessler/M +Kettering/M +Ketti/M +Kettie/M +Ketty/M +Kev/MN +Kevan/M +Keven/M +Kevin/M +Kevina/M +Kevlar +Kevon/M +Kevorkian/M +Kevyn/M +Kewaskum/M +Kewaunee/M +Kewpie/M +Key/M +Keynes/M +Keynesian/M +Khabarovsk/M +Khachaturian/M +Khalid/M +Khalil/M +Khan/M +Kharkov/M +Khartoum/M +Khayyam/M +Khmer/M +Khoisan/M +Khomeini/M +Khorana/M +Khrushchev/SM +Khufu/M +Khulna/M +Khwarizmi/M +Khyber/M +Ki/M +Kiah/M +Kial/M +Kickapoo/M +Kidd/M +Kieffer/M +Kiel/M +Kiele/M +Kienan/M +Kierkegaard/M +Kiersten/M +Kieth/M +Kiev/M +Kigali/M +Kikelia/M +Kikuyu/M +Kilauea/M +Kile/M +Kiley/M +Kilian/M +Kilimanjaro/M +Killebrew/M +Killian/M +Killie/M +Killy/M +Kim/M +Kimball/M +Kimbell/M +Kimberlee/M +Kimberley/M +Kimberli/M +Kimberly/M +Kimberlyn/M +Kimble/M +Kimbra/M +Kimmi/M +Kimmie/M +Kimmy/M +Kin/M +Kincaid/M +King/M +Kingsbury/M +Kingsley/M +Kingsly/M +Kingston/M +Kingstown/M +Kingwood/M +Kinna/M +Kinney/M +Kinnickinnic/M +Kinnie/M +Kinny/M +Kinsey/M +Kinshasa/M +Kinshasha/M +Kinsley/M +Kiowa/SM +Kip/M +Kipling/M +Kipp/MR +Kippar/M +Kipper/M +Kippie/M +Kippy/M +Kira/M +Kirbee/M +Kirbie/M +Kirby/M +Kirchhoff/M +Kirchner/M +Kirchoff/M +Kirghistan/M +Kirghiz/M +Kirghizia/M +Kiri/M +Kiribati +Kirinyaga/M +Kirk/M +Kirkland/M +Kirkpatrick/M +Kirkwood/M +Kirov/M +Kirsten/M +Kirsteni/M +Kirsti/M +Kirstin/M +Kirstyn/M +Kisangani/M +Kishinev/M +Kissee/M +Kissiah/M +Kissie/M +Kissinger/M +Kit/M +Kitakyushu/M +Kitchener/M +Kitti/M +Kittie/M +Kitty/M +Kiwanis/M +Kizzee/M +Kizzie/M +Klan/M +Klansman/M +Klara/M +Klarika/M +Klarrisa/M +Klaus/M +Klee/M +Kleenex/SM +Klein/M +Kleinrock/M +Klemens/M +Klement/M +Kleon/M +Kliment/M +Kline/M +Klingon/M +Klondike/SDMG +Klux/M +Knapp/M +Knauer/M +Knesset/M +Kngwarreye/M +Knickerbocker/MS +Knievel/M +Knight/M +Knobeloch/M +Knopf/M +Knossos/M +Knowles +Knox/M +Knoxville/M +Knudsen/M +Knudson/M +Knuth/M +Knutsen/M +Knutson/M +Kobayashi/M +Kobe/M +Koch/M +Kochab/M +Kodachrome/M +Kodak/SM +Kodaly/M +Kodiak/M +Koenig/M +Koenigsberg/M +Koenraad/M +Koestler/M +Kohinoor/M +Kohl/MR +Kohler/M +Kolyma/M +Kommunizma/M +Kong/M +Kongo/M +Konrad/M +Konstance/M +Konstantin/M +Konstantine/M +Konstanze/M +Koo/M +Koontz/M +Koppers/M +Kora/M +Koral/M +Koralle/M +Koran/SM +Koranic +Kordula/M +Kore/M +Korea/M +Korean/S +Korella/M +Koren/M +Koressa/M +Korey/M +Kori/M +Korie/M +Kornberg/M +Korney/M +Korrie/M +Korry/M +Kort/M +Kory/M +Korzybski/M +Kosciusko/M +Kossuth/M +Kosygin/M +Kovacs/M +Kowalewski/M +Kowalski/M +Kowloon/M +Kr/M +Kraemer/M +Kraft/M +Krakatau's +Krakatoa/M +Krakow/M +Kramer/M +Krasnodar/M +Krasnoyarsk/M +Krause/M +Krebs/M +Kremlin/M +Kremlinologist/MS +Kremlinology/MS +Kresge/M +Krieger/M +Kringle/M +Kris/M +Krisha/M +Krishna/M +Krishnah/M +Krispin/M +Krissie/M +Krissy/M +Krista/M +Kristal/M +Kristan/M +Kriste/M +Kristel/M +Kristen/M +Kristi/MN +Kristian/M +Kristie/M +Kristien/M +Kristin/M +Kristina/M +Kristine/M +Kristo/MS +Kristofer/M +Kristoffer/M +Kristofor/M +Kristoforo/M +Kristopher/M +Kristy/M +Kristyn/M +Kroc/M +Kroger/M +Kronecker/M +Kropotkin/M +Krueger/M +Kruger/M +Krugerrand/S +Krupp/M +Kruse/M +Krysta/M +Krystal/M +Krystalle/M +Krystle/M +Krystyna/M +Ku/M +Kublai/M +Kubrick/M +Kubuntu +Kubuntu's +Kuenning/M +Kuhn/M +Kuibyshev/M +Kumar/M +Kunming/M +Kuomintang/M +Kurd/SM +Kurdish/M +Kurdistan/SM +Kurosawa/M +Kurt/M +Kurtis/M +Kusch/M +Kuwait/M +Kuwaiti/SM +Kuznets/M +Kuznetsk/M +Kwakiutl/M +Kwangchow's +Kwangju/M +Kwanzaa/S +Ky/MH +Kyla/M +Kyle/M +Kylen/M +Kylie/M +Kylila/M +Kylynn/M +Kym/M +Kynthia/M +Kyoto/M +Kyrgyzstan +Kyrstin/M +Kyushu/M +L +L'Amour +L'Ouverture +L's +L'vov +LA +LAN +LBJ/M +LC +LCD +LCM +LDC +LED/SM +LIFO +LL +LLB +LLD +LNG +LOGO +LP +LPG +LPN/S +LSD +La/M +LaTeX/M +Lab/SM +Laban/M +Labrador/SM +Labradorean/S +Lac/M +Lacee/M +Lacey/M +Lachesis/M +Lacie/M +Lackawanna/M +Lacy/M +Ladoga/M +Ladonna/M +Lady/SM +Ladyship/MS +Laetitia/M +Lafayette/M +Lafitte/M +Lagos/M +Lagrange/M +Lagrangian/M +Laguerre/M +Laguna/M +Lahore/M +Laidlaw/M +Laina/M +Lainey/M +Laird/M +Laius/M +Lakehurst/M +Lakeisha/M +Lakewood/M +Lakisha/M +Lakshmi/M +Lalo/M +Lamaism/SM +Lamar/M +Lamarck/M +Lamaze +Lamb/M +Lambert/M +Lamborghini/M +Lammond/M +Lamond/M +Lamont/M +Lamport/M +Lana/M +Lanae/M +Lanai/M +Lancashire/M +Lancaster/M +Lance/M +Lancelot/M +Land/M +Landis/M +Landon/M +Landry/M +Landsat +Landsteiner/M +Landwehr/M +Lane/M +Lanette/M +Laney/M +Lang/M +Lange/M +Langeland/M +Langerhans/M +Langford/M +Langland/M +Langley/M +Langmuir/M +Langsdon/M +Langston/M +Lani/M +Lanie/M +Lanita/M +Lanna/M +Lanni/M +Lannie/M +Lanny/M +Lansing/M +Lanzhou +Lao/SM +Laocoon/M +Laotian/MS +Laplace/M +Lapland/ZMR +Lapp/SM +Lara/M +Laraine/M +Laramie/M +Lardner/M +Laredo/M +Lari/M +Larina/M +Larine/M +Larisa/M +Larissa/M +Lark/M +Larousse/M +Larry/M +Lars/NM +Larsen/M +Larson/M +Laryssa/M +Lascaux/M +Lassa/M +Lassen/M +Lassie/M +Laszlo/M +Lat/M +Latasha/M +Latashia/M +Lateran/M +Lathrop/M +Latia/M +Latin/RMS +Latina/SM +Latinate +Latino/S +Latisha/M +Latonya/M +Latoya/M +Latrena/M +Latrina/M +Latrobe/M +Lattimer/M +Latvia/M +Latvian/S +Laud/MR +Lauder/M +Lauderdale/M +Laue/M +Laughton/M +Launce/M +Laundromat/SM +Laura/M +Lauraine/M +Laural/M +Lauralee/M +Laurasia/M +Laure/M +Lauree/M +Laureen/M +Laurel/M +Laurella/M +Lauren/SM +Laurena/M +Laurence/M +Laurene/M +Laurent/M +Laurentian +Lauretta/M +Laurette/M +Lauri/M +Laurianne/M +Laurice/M +Laurie/M +Lauritz/M +Lauryn/M +Lausanne/M +Laval/M +Lavena/M +Lavern/M +Laverna/M +Laverne/M +Lavina/M +Lavinia/M +Lavinie/M +Lavoisier/M +Lavonne/M +Law/M +Lawanda/M +Lawford/M +Lawrence/M +Lawrenceville/M +Lawry/M +Lawson/M +Lawton/M +Lay/M +Layamon/M +Layla/M +Layne/M +Layney/M +Layton/M +Lazar/M +Lazare/M +Lazaro/M +Lazarus/M +Le/NM +Lea/M +Leach/M +Leadbelly/M +Leah/M +Leakey/M +Lean/M +Leander/M +Leandra/M +Leann/M +Leanna/M +Leanne/M +Leanor/M +Leanora/M +Lear/M +Leary/M +Leavenworth/M +Lebanese +Lebanon/M +Lebbie/M +Lebesgue/M +Leblanc/M +Leda/M +Lederberg/M +Lee/M +Leeann/M +Leeanne/M +Leeds/M +Leela/M +Leelah/M +Leeland/M +Leena/M +Leesa/M +Leese/M +Leeuwenhoek/M +Leeward/M +Left/S +Lefty/M +Legendre/M +Leger/SM +Leghorn/SM +Lego/M +Legra/M +Legree/M +Lehigh/M +Lehman/M +Leia/M +Leibniz/M +Leicester/SM +Leiden/M +Leif/M +Leigh/M +Leigha/M +Leighton/M +Leila/M +Leilah/M +Leipzig/M +Leisha/M +Lek/M +Lela/M +Lelah/M +Leland/M +Lelia/M +Lem/M +Lemaitre/M +Lemar/M +Lemke/M +Lemmie/M +Lemmy/M +Lemuel/M +Lemuria/M +Len/M +Lena/M +Lenard/M +Lenci/M +Lenee/M +Lenette/M +Lenin/M +Leningrad/M +Leninism/M +Leninist +Lenka/M +Lenna/M +Lennard/M +Lennie/M +Lennon/M +Lenny/M +Leno/M +Lenoir/M +Lenora/M +Lenore/M +Lent/SMN +Leo/MS +Leodora/M +Leoine/M +Leola/M +Leoline/M +Leon/M +Leona/M +Leonanie/M +Leonard/M +Leonardo/M +Leoncavallo/M +Leone/M +Leonel/M +Leonelle/M +Leonerd/M +Leonhard/M +Leonid/M +Leonidas/M +Leonie/M +Leonor/M +Leonora/M +Leonore/M +Leontine/M +Leontyne/M +Leopold/M +Leopoldo/M +Leopoldville/M +Leora/M +Lepidus/M +Lepke/M +Lepus/M +Lerner/M +Leroi/M +Leroy/M +Les/Y +Lesa/M +Leshia/M +Lesley/M +Lesli/M +Leslie/M +Lesly/M +Lesotho/M +Lesseps/M +Lessie/M +Lester/M +Lesya/M +Leta/M +Letha/M +Lethe/M +Lethia/M +Leticia/M +Letisha/M +Letitia/M +Letizia/M +Letta/M +Letterman/M +Letti/M +Lettie/M +Letty/M +Leupold/M +Lev/M +Levant/M +Levesque/M +Levey/M +Levi/MS +Leviathan +Levin/M +Levine/M +Leviticus/M +Levitt/M +Levon/M +Levy/M +Lew/M +Lewellyn/M +Lewes +Lewie/M +Lewinsky/M +Lewis/M +Lewiss +Lexi/MS +Lexie/M +Lexine/M +Lexington/M +Lexus/M +Lexy/M +Leyden/M +Leyla/M +Lezley/M +Lezlie/M +Lhasa/SM +Lhotse/M +Li/MY +Lia/M +Liam/M +Lian/M +Liana/M +Liane/M +Lianna/M +Lianne/M +Lib/M +Libbey/M +Libbi/M +Libbie/M +Libby/M +Liberace/M +Liberia/M +Liberian/S +Libra/SM +Libreville/M +Librium/M +Libya/M +Libyan/S +Licha/M +Lichtenstein/M +Lichter/M +Lida/M +Lidia/M +Lie/M +Lieberman/M +Liebfraumilch/M +Liechtenstein/RMZ +Lief/M +Liege/M +Liesa/M +Lieut/M +Lil/MY +Lila/SM +Lilah/M +Lilia/MS +Lilian/M +Liliana/M +Liliane/M +Lilith/M +Liliuokalani/M +Lilla/M +Lille/M +Lilli/MS +Lillian/M +Lillie/M +Lilliput/M +Lilliputian/SM +Lilllie/M +Lilly/M +Lilongwe/M +Lily/M +Lilyan/M +Lima/M +Limbaugh/M +Limbo +Limburger/SM +Limoges/M +Limpopo/M +Lin/M +Lina/M +Linc/M +Lincoln/SM +Lind/M +Linda/M +Lindberg/M +Lindbergh/M +Lindholm/M +Lindi/M +Lindie/M +Lindon/M +Lindquist/M +Lindsay/M +Lindsey/M +Lindstrom/M +Lindsy/M +Lindy/M +Linea/M +Linell/M +Linet/M +Linette/M +Link/M +Linn/M +Linnaeus/M +Linnea/M +Linnell/M +Linnet/M +Linnie/M +Linoel/M +Linotype/M +Linton/M +Linus/M +Linux/M +Linwood/M +Linzy/M +Lion/M +Lionel/M +Lionello/M +Lippi/M +Lippmann/M +Lipschitz/M +Lipscomb/M +Lipton/M +Lira/M +Lisa/M +Lisabeth/M +Lisbeth/M +Lisbon/M +Lise/M +Lisetta/M +Lisette/M +Lisha/M +Lishe/M +Lisle/M +Liss/M +Lissa/M +Lissajous/M +Lissi/M +Lissie/M +Lissy/M +Lister/M +Listerine/M +Liston/M +Liszt/M +Lita/M +Lithuania/M +Lithuanian/S +Little/M +Littleton/M +Litton/M +Liuka/M +Liv/M +Liva/M +Livermore/M +Liverpool/M +Liverpudlian/MS +Livia/M +Livingston/M +Livingstone/M +Livonia/M +Livvie/M +Livvy/M +Livvyy/M +Livy/M +Liz/M +Liza/M +Lizabeth/M +Lizbeth/M +Lizette/M +Lizzie/M +Lizzy/M +Ljubljana/M +Llewellyn/M +Lloyd/M +Llywellyn/M +Loafer/S +Lobachevsky/M +Lochinvar/M +Lock/M +Locke/M +Lockean/M +Lockhart/M +Lockheed/M +Lockian/M +Lockwood/M +Lodge/M +Lodovico/M +Lodowick/M +Lodz +Loeb/M +Loella/M +Loewe/M +Loewi/M +Logan/M +Lohengrin/M +Loire/M +Lois/M +Loise/M +Loki/M +Lola/M +Loleta/M +Lolita/M +Lolly/M +Lomb/M +Lombard/M +Lombardi/M +Lombardy/M +Lome +Lon/M +Lona/M +London/RMZ +Londonderry/M +Londoner/M +Lonee/M +Long/M +Longfellow/M +Longstreet/M +Longueuil/M +Loni/M +Lonna/M +Lonnard/M +Lonni/M +Lonnie/M +Lonny/M +Loomis/M +Lopez/M +Lora/M +Lorain/M +Loraine/M +Loralee/M +Loralie/M +Loralyn/M +Lorant/M +Lord/MS +Lordship/SM +Loree/M +Loreen/M +Lorelei/M +Lorelle/M +Loren/SM +Lorena/M +Lorene/M +Lorentz/M +Lorentzian/M +Lorenz/M +Lorenza/M +Lorenzo/M +Loretta/M +Lorette/M +Lori/M +Loria/M +Lorianna/M +Lorianne/M +Lorie/M +Lorilee/M +Lorilyn/M +Lorin/M +Lorinda/M +Lorine/M +Lorita/M +Lorna/M +Lorne/M +Lorraine/M +Lorrayne/M +Lorre/M +Lorri/M +Lorrie/M +Lorrin/M +Lorry/M +Lory/M +Los +Lot/M +Lothaire/M +Lothario/MS +Lott/M +Lotta/M +Lotte/M +Lotti/M +Lottie/M +Lotty/M +Lou/M +Louella/M +Louie/M +Louis/M +Louisa/M +Louise/M +Louisette/M +Louisiana/M +Louisianan/S +Louisianian/S +Louisville/M +Lourdes/M +Loutitia/M +Louvre/M +Love/M +Lovecraft/M +Lovejoy/M +Lovelace/M +Loveland/M +Lovell/M +Lowe/M +Lowell/M +Lowery/M +Lowlands/M +Lowrance/M +Loy/M +Loyang/M +Loyd/M +Loydie/M +Loyola/M +Lr +Lt/M +Ltd/M +Lu/M +Luanda/M +Luann/M +Lubbock/M +Lubumbashi/M +Luca/MS +Lucais/M +Luce/M +Lucerne/M +Lucho/M +Luci/MN +Lucia/MS +Lucian/M +Luciana/M +Luciano/M +Lucie/M +Lucien/M +Lucienne/M +Lucifer/M +Lucila/M +Lucile/M +Lucilia/M +Lucille/M +Lucina/M +Lucinda/M +Lucine/M +Lucio/M +Lucita/M +Lucite/MS +Lucius/M +Lucknow/M +Lucky/M +Lucretia/M +Lucretius/M +Lucy/M +Luddite/SM +Ludhiana/M +Ludlow/M +Ludmilla/M +Ludovico/M +Ludovika/M +Ludvig/M +Ludwig/M +Luella/M +Luelle/M +Lufthansa/M +Luftwaffe/M +Luger/M +Lugosi/M +Luigi/M +Luis/M +Luisa/M +Luise/M +Lukas/M +Luke/M +Lula/M +Lulita/M +Lulu/M +Lumière/M +Luna/M +Lund/M +Lundberg/M +Lundquist/M +Lupe/M +Lupus/M +Lura/M +Lurette/M +Luria/M +Lurleen/M +Lurlene/M +Lurline/M +Lusa/M +Lusaka/M +Lusitania/M +Lutero/M +Luther/M +Lutheran/SM +Lutheranism/MS +Lutz +Luxembourg/RMZ +Luxembourgian +Luxemburg's +Luz/M +Luzon/M +Ly/MY +LyX/M +Lyallpur/M +Lycra/S +Lycurgus/M +Lyda/M +Lydia/M +Lydian/S +Lydie/M +Lydon/M +Lyell/M +Lyle/M +Lyly/M +Lyman/M +Lyme/M +Lyn/M +Lynch/M +Lynchburg/M +Lynda/M +Lynde/M +Lyndel/M +Lyndell/M +Lyndon/M +Lyndsay/M +Lyndsey/M +Lyndsie/M +Lyndy/M +Lynea/M +Lynelle/M +Lynett/M +Lynette/M +Lynn/M +Lynna/M +Lynne/M +Lynnea/M +Lynnell/M +Lynnelle/M +Lynnet/M +Lynnett/M +Lynnette/M +Lynsey/M +Lyon/SM +Lyra/M +Lysenko/M +Lysistrata/M +Lysol/M +Lyssa/M +M's +M/GB +MA +MAG +MASH +MB +MBA +MC +MCI/M +MD +MDT +ME +MFA +MGM/M +MHz +MI +MIA +MIG/S +MIMD +MIPS +MIRV/DSG +MIT/M +MITRE/SM +MM +MMe +MN +MO +MP +MPH +MRI +MS +MSG +MST +MSW +MT +MTS +MTV +MULTICS/M +MVP +MW +Maalox/M +Mab/M +Mabel/M +Mabelle/M +Mable/M +Mac/SGMD +MacArthur/M +MacDonald/M +MacDraw/M +MacGregor/M +MacIntosh/M +MacKenzie/M +MacLeish/M +MacMillan/M +MacPaint/M +Macao/M +Macarthur/M +Macaulay/M +Macbeth/M +Maccabees/M +Maccabeus/M +Macdonald/M +Mace/MS +Macedon/M +Macedonia/M +Macedonian/S +Macgregor/M +Mach/M +Machiavelli/M +Machiavellian/S +Machs +Macias/M +Macintosh/M +Mack/M +Mackenzie/M +Mackinac/M +Mackinaw +Macmillan/M +Macon/SM +Macy/M +Mada/M +Madagascan/SM +Madagascar/M +Madalena/M +Madalyn/M +Madame/MS +Maddalena/M +Madden/M +Maddi/M +Maddie/M +Maddox/M +Maddy/M +Madeira/SM +Madel/M +Madelaine/M +Madeleine/M +Madelena/M +Madelene/M +Madelin/M +Madelina/M +Madeline/M +Madella/M +Madelle/M +Madelon/M +Madelyn/M +Madge/M +Madhya/M +Madison/M +Madlen/M +Madlin/M +Madonna/MS +Madras +Madrid/M +Madsen/M +Madurai/M +Mady/M +Mae/M +Maegan/M +Maelstrom/M +Maeterlinck/M +Mafia/MS +Mafioso/S +Mag/M +Magda/M +Magdaia/M +Magdalen/M +Magdalena/M +Magdalene/M +Magellan/M +Magellanic +Maggee/M +Maggi/M +Maggie/M +Maggy/M +Magi/M +Magill/M +Maginot/M +Magnitogorsk/M +Magnum +Magnuson/M +Magog/M +Magoo/M +Magritte/M +Magruder/M +Magsaysay/M +Maguire/SM +Magus/M +Magyar/MS +Mahabharata +Mahala/M +Mahalia/M +Maharashtra/M +Mahavira/M +Mahayana/M +Mahayanist +Mahdi/M +Mahfouz/M +Mahican/SM +Mahler/M +Mahmoud/M +Mahmud/M +Mahomet's +Mai/MR +Maia/M +Maible/M +Maier/M +Maiga/M +Maighdiln/M +Maigret/M +Mailer/M +Maillol/M +Maiman/M +Maimonides/M +Maine/MZR +Mainer/M +Mair/M +Maire/M +Maisey/M +Maisie/M +Maison/M +Maitilde/M +Maj +Maje/M +Majesty/MS +Major/M +Majorca/M +Majuro/M +Makarios/M +Maker/M +Mal/M +Mala/M +Malabar/M +Malabo/M +Malacca/M +Malachi/M +Malagasy/M +Malamud/M +Malanie/M +Malaprop/M +Malawi/M +Malawian/S +Malay/SM +Malaya/M +Malayalam/M +Malayan/MS +Malaysia/M +Malaysian/S +Malchy/M +Malcolm/M +Maldive/SM +Maldivian/S +Maldonado/M +Male/M +Malena/M +Mali/M +Malia/M +Malian/S +Malibu/M +Malina/M +Malinda/M +Malinde/M +Malinowski/M +Malissa/M +Malissia/M +Mallarmé/M +Mallissa/M +Mallorie/M +Mallory/M +Malone/M +Malorie/M +Malory/M +Malraux/M +Malta/M +Maltese +Malthus/M +Malthusian/S +Malva/M +Malvin/M +Malvina/M +Malynda/M +Mame/M +Mamet/M +Mamie/M +Mammon's +Mamore/M +Man/SM +Managua/M +Manama/M +Manasseh/M +Manaus's +Manchester/M +Manchu/MS +Manchuria/M +Manchurian/S +Mancini/M +Mancunian/MS +Manda/M +Mandalay/M +Mandarin +Mandel/M +Mandela +Mandelbrot/M +Mandi/M +Mandie/M +Mandingo/M +Mandy/M +Manet/M +Manfred/M +Manhattan/SM +Mani/M +Manichean/M +Manila/MS +Manitoba/M +Manitoulin/M +Manitowoc/M +Mankowski/M +Manley/M +Mann/GM +Mannheim/M +Mannie/M +Manning/M +Manny/M +Mano/M +Manolo/M +Manon/M +Mansfield/M +Manson/M +Mantegna/M +Mantle/M +Manuel/M +Manuela/M +Manville/M +Manx +Manya/M +Mao/M +Maoism/MS +Maoist/S +Maori/SM +Maplecrest/M +Mapplethorpe/M +Maputo/M +Mar/SMN +Mara/M +Marabel/M +Maracaibo/M +Marat/M +Marathi +Marathon/M +Marc/M +Marceau/M +Marcel/M +Marcela/M +Marcelia/M +Marcelino/M +Marcella/M +Marcelle/M +Marcellina/M +Marcelline/M +Marcello/M +Marcellus/M +Marcelo/M +March/MS +Marchall/M +Marchelle/M +Marci/M +Marcia/M +Marciano/M +Marcie/M +Marcile/M +Marcille/M +Marco/SM +Marconi/M +Marcotte/M +Marcus/M +Marcy/M +Mardi/SM +Marduk/M +Mareah/M +Maren/M +Marena/M +Maressa/M +Marga/M +Margalit/M +Margalo/M +Margaret/M +Margareta/M +Margarete/M +Margaretha/M +Margarethe/M +Margaretta/M +Margarette/M +Margarita/M +Margarito/M +Margaux/M +Marge/M +Margeaux/M +Margery/M +Marget/M +Margette/M +Margi/M +Margie/M +Margit/M +Margo/M +Margot/M +Margret/M +Margrethe/M +Marguerite/M +Margy/M +Mari/MS +Maria/M +Mariam/M +Marian/MS +Mariana/SM +Mariann/M +Marianna/M +Marianne/M +Mariano/M +Maribel/M +Maribelle/M +Maribeth/M +Marice/M +Maricela/M +Maridel/M +Marie/M +Marieann/M +Mariejeanne/M +Mariel/M +Mariele/M +Marielle/M +Mariellen/M +Marietta/M +Mariette/M +Marigold/M +Marijn/M +Marijo/M +Marika/M +Marilee/M +Marilin/M +Marillin/M +Marilyn/M +Marin/M +Marina/M +Marine/S +Marinna/M +Marino/M +Mario/M +Marion/M +Mariquilla/M +Marisa/M +Mariska/M +Marisol/M +Marissa/M +Marita/M +Maritain/M +Maritsa/M +Maritza/M +Mariupol/M +Marius/M +Mariya/M +Marj/M +Marja/M +Marje/M +Marji/M +Marjie/M +Marjorie/M +Marjory/M +Marjy/M +Mark/MS +Markab/M +Marketa/M +Markham/M +Markism/M +Markos +Markov +Markovian +Markovitz/M +Markus/M +Marla/M +Marlane/M +Marlboro/M +Marlborough/M +Marleah/M +Marlee/M +Marleen/M +Marlena/M +Marlene/M +Marley/M +Marlie/M +Marlin/M +Marline/M +Marlo/M +Marlon/M +Marlow/M +Marlowe/M +Marlyn/M +Marmaduke/M +Marmara/M +Marna/M +Marne/M +Marney/M +Marni/M +Marnia/M +Marnie/M +Marquesas/M +Marquette/M +Marquez/M +Marquis/M +Marquita/M +Marrakesh/M +Marrilee/M +Marriott/M +Marris/M +Marrissa/M +Marseillaise/SM +Marseille's +Marseilles +Marsh/M +Marsha/M +Marshal/M +Marshall/GDM +Marshalled/M +Marshalling/M +Marsiella/M +Mart/MN +Marta/M +Martainn/M +Martel/M +Martelle/M +Marten/M +Martguerita/M +Martha/M +Marthe/M +Marthena/M +Marti/M +Martial +Martian/S +Martica/M +Martie/M +Martin/M +Martina/M +Martinez/M +Martinique/M +Martino/M +Martinson/M +Martita/M +Marty/M +Martyn/M +Martynne/M +Marv/NM +Marva/M +Marve/M +Marvell/M +Marven/M +Marvin/M +Marwin/M +Marx/M +Marxian/S +Marxism/SM +Marxist/SM +Mary/M +Marya/M +Maryann/M +Maryanna/M +Maryanne/M +Marybelle/M +Marybeth/M +Maryellen/M +Maryjane/M +Maryjo/M +Maryl/M +Maryland/MZR +Marylee/M +Marylin/M +Marylinda/M +Marylou/M +Marylynne/M +Maryrose/M +Marys +Marysa/M +Masada/M +Masai/M +Masaryk/M +Mascagni/M +Masefield/M +Maseru/M +Masha/M +Mashhad/M +Mason/SM +Masonic +Masonite/M +Mass/S +Massachusetts/M +Massasoit/M +Massenet/M +Massey/M +Massimiliano/M +Massimo/M +Master/SM +Mata/M +Matelda/M +Mateo/M +Mathe/RM +Mathematica/M +Mathematik/M +Mather/M +Mathew/MS +Mathewson/M +Mathian/M +Mathias +Mathieu/M +Mathilda/M +Mathilde/M +Mathis +Matias/M +Matilda/M +Matilde/M +Matisse/SM +Matsumoto/M +Matt/M +Mattel/M +Matteo/M +Matterhorn/M +Matthaeus/M +Mattheus/M +Matthew/MS +Matthias +Matthieu/M +Matthiew/M +Matthus/M +Matti/M +Mattias/M +Mattie/M +Matty/M +Maud/M +Maude/M +Maudie/M +Maugham/M +Maui/M +Maupassant/M +Maura/M +Maure/M +Maureen/M +Maureene/M +Maurene/M +Mauriac/M +Maurice/M +Mauricio/M +Maurie/M +Maurine/M +Maurise/M +Maurita/M +Mauritania/M +Mauritanian/S +Mauritian/S +Mauritius/M +Maurits/M +Maurizia/M +Maurizio/M +Mauro/M +Maurois/M +Maury/M +Mauser/M +Mavis/M +Mavra/M +Mawr/M +Max/M +Maxi/M +Maxie/M +Maxim/M +Maximilian/M +Maximilianus/M +Maximilien/M +Maximo/M +Maxine/M +Maxtor/M +Maxwell/M +Maxwellian +Maxy/M +May/SMR +Maya/MS +Mayan/S +Maybelle/M +Maye/M +Mayer/M +Mayfair/M +Mayflower/M +Maynard/M +Mayne/M +Maynord/M +Mayo/M +Mayor/M +Maypole/SM +Mayra/M +Mazama/M +Mazarin/M +Mazatlan/M +Mazda/M +Mazzini/M +Mb +Mbabane/M +Mbini/M +McAdam/MS +McAllister/M +McBride/M +McCabe/M +McCain/M +McCall/M +McCarthy/M +McCarthyism/M +McCartney/M +McCarty/M +McCauley/M +McClain/M +McClellan/M +McClure/M +McCluskey/M +McConnell/M +McCormick/M +McCoy/SM +McCracken/M +McCray/M +McCullough/M +McDaniel/M +McDermott/M +McDonald/M +McDonnell/M +McDougall/M +McDowell/M +McElhaney/M +McEnroe/M +McFadden/M +McFarland/M +McGee/M +McGill/M +McGovern/M +McGowan/M +McGrath/M +McGraw/M +McGregor/M +McGuffey/M +McGuire/M +McIntosh/M +McIntyre/M +McKay/M +McKee/M +McKenzie/M +McKesson/M +McKinley/M +McKinney/M +McKnight/M +McLanahan/M +McLaughlin/M +McLean/M +McLeod/M +McLuhan/M +McMahon/M +McMartin/M +McMillan/M +McNamara/M +McNaughton/M +McNeil/M +McPherson/M +Md/M +Me/M +Mead/M +Meade/M +Meadows +Meagan/M +Meaghan/M +Meany/M +Meara/M +Mecca/MS +Mechelle/M +Medan/M +Medea/M +Medellin +Medfield/M +Medicaid/SM +Medicare/MS +Medici/MS +Medina/M +Mediterranean/MS +Medusa/M +Meg/MN +Megan/M +Megen/M +Meggi/M +Meggie/M +Meggy/M +Meghan/M +Meghann/M +Mehetabel/M +Mei/MR +Meier/M +Meighen/M +Meiji/M +Meir/M +Meister/M +Meistersinger/M +Mejia/M +Mekong/M +Mel/MY +Mela/M +Melamie/M +Melanesia/M +Melanesian/S +Melania/M +Melanie/M +Melantha/M +Melany/M +Melba/M +Melbourne/M +Melcher/M +Melchior/M +Melendez/M +Melesa/M +Melessa/M +Melicent/M +Melina/M +Melinda/M +Melinde/M +Melisa/M +Melisande/M +Melisandra/M +Melisenda/M +Melisent/M +Melissa/M +Melisse/M +Melita/M +Melitta/M +Mella/M +Melli/M +Mellicent/M +Mellie/M +Mellisa/M +Mellisent/M +Mellon/M +Melloney/M +Melly/M +Melodee/M +Melodie/M +Melody/M +Melonie/M +Melony/M +Melosa/M +Melpomene/M +Melton/M +Melva/M +Melville/M +Melvin/M +Melvyn/M +Memling/M +Memphis/M +Menander/M +Menard/M +Mencius/M +Mencken/M +Mendel/M +Mendeleev/M +Mendelian +Mendelssohn/M +Mendez/M +Mendie/M +Mendocino/M +Mendoza/M +Mendy/M +Menelaus/M +Menes/M +Menkalinan/M +Menkar/M +Menkent/M +Menlo/M +Mennonite/SM +Menominee +Menotti/M +Mensa/M +Mensch/M +Menuhin/M +Menzies/M +Mephistopheles/M +Merak/M +Mercado/M +Mercator/M +Mercedes +Mercer/M +Merci/M +Mercie/M +Merck/M +Mercurochrome/M +Mercury/MS +Mercy/M +Meredeth/M +Meredith/M +Meredithe/M +Merell/M +Meridel/M +Meridith/M +Meriel/M +Merilee/M +Merill/M +Merilyn/M +Meris +Merissa/M +Meriwether/M +Merl/M +Merla/M +Merle/M +Merlin/M +Merlina/M +Merline/M +Merna/M +Merola/M +Merralee/M +Merrel/M +Merriam/M +Merrick/M +Merridie/M +Merrie/M +Merrielle/M +Merrile/M +Merrilee/M +Merrili/M +Merrill/M +Merrily/M +Merrimac/M +Merrimack/M +Merritt/M +Merry/M +Mersey/M +Merton/M +Merv/M +Mervin/M +Merwin/M +Merwyn/M +Meryl/M +Mesa +Mesabi/M +Meshed's +Mesolithic/M +Mesopotamia/M +Mesopotamian/S +Mesozoic +Messerschmidt/M +Messiaen/M +Messiah/M +Messiahs +Messianic +Messrs/M +Meta/M +Methodism/SM +Methodist/MS +Methuen/M +Methuselah/M +Methuselahs +Metrecal/M +Metternich/M +Metzler/M +Meuse/M +Mex +Mexicali/M +Mexican/S +Mexico/M +Meyer/SM +Meyerbeer/M +Mg/M +Mgr +Mia/M +Miami/SM +Miaplacidus/M +Mic/M +Micaela/M +Micah/M +Mich/M +Michael/SM +Michaela/M +Michaelangelo/M +Michaelina/M +Michaeline/M +Michaella/M +Michaelmas/MS +Michaelson/M +Michail/M +Michal/M +Michale/M +Micheal/M +Micheil/M +Michel/M +Michelangelo/M +Michele/M +Michelin/M +Michelina/M +Micheline/M +Michell/M +Michelle/M +Michelson/M +Michigan/M +Michigander/S +Michiganite/S +Mick/M +Mickelson/M +Mickey/M +Micki/M +Mickie/M +Micky/M +Micmac/M +MicroVAX/M +MicroVAXes +Micronesia/M +Micronesian/S +Microport/M +Microsoft/M +Microsystems +Midas/M +Middlebury/M +Middlesex/M +Middleton/M +Middletown/M +Mideast/M +Mideastern +Midge/M +Midland/MS +Midway/M +Midwest/M +Midwestern/ZR +Midwesterner/M +Mignon/M +Mignonne/M +Miguel/M +Miguela/M +Miguelita/M +Mikael/M +Mikaela/M +Mike/M +Mikel/M +Mikey/M +Mikhail/M +Mikkel/M +Mikol/M +Mikoyan/M +Mil/MY +Milagros/M +Milan/M +Milanese +Mildred/M +Mildrid/M +Mile/SM +Milena/M +Milford/M +Milicent/M +Milissent/M +Milka/M +Milken/M +Mill/SMR +Millard/M +Millay/M +Miller/M +Millet/M +Milli/M +Millicent/M +Millie/M +Millikan/M +Millisent/M +Milly/M +Milne/M +Milo/M +Milquetoast/S +Milt/M +Miltiades/M +Miltie/M +Milton/M +Miltonic +Miltown/M +Milty/M +Milwaukee/M +Milzie/M +Mimi/M +Mimosa/M +Min/MR +Mina/M +Minda/M +Mindanao/M +Mindoro/M +Mindy/M +Miner/M +Minerva/M +Minetta/M +Minette/M +Ming/M +Mingus/M +Minn/M +Minna/M +Minnaminnie/M +Minne/M +Minneapolis/M +Minnesota/M +Minnesotan/S +Minni/M +Minnie/M +Minnnie/M +Minny/M +Minoan/S +Minolta/M +Minor/M +Minos +Minot/M +Minotaur/M +Minsk/M +Minsky/M +Minta/M +Mintaka/M +Minuit/M +Minuteman/M +Miocene +Miquela/M +Mir/M +Mira/M +Mirabeau/M +Mirabel/M +Mirabella/M +Mirabelle/M +Mirach/M +Miran/M +Miranda/M +Mireielle/M +Mireille/M +Mirella/M +Mirelle/M +Mirfak/M +Miriam/M +Mirilla/M +Mirna/M +Miro +Mirzam/M +Mischa/M +Misha/M +Miskito +Miss/SM +Missie/M +Mississauga/M +Mississippi/M +Mississippian/S +Missoula/M +Missouri/M +Missourian/S +Missy/M +Mistassini/M +Mister/SM +Misti/M +Mistress/MS +Misty/M +Mitch/M +Mitchael/M +Mitchel/M +Mitchell/M +Mitford/M +Mithra/M +Mithridates/M +Mitsubishi/M +Mitterrand/M +Mitty/M +Mitzi/M +Mizar/M +Mk +Mlle/M +Mme/SM +Mn/M +Mnemosyne/M +Mo/MN +Mobil/M +Mobile/M +Mobutu/M +Modesta/M +Modestia/M +Modestine/M +Modesto/M +Modesty/M +Modigliani/M +Modula/M +Moe/M +Moen/M +Mogadiscio's +Mogadishu +Mogul/MS +Mohamed/M +Mohammad/M +Mohammed's +Mohammedan/SM +Mohammedanism/MS +Mohandas/M +Mohandis/M +Mohawk/MS +Mohegan/S +Mohican's +Moho/M +Mohorovicic/M +Mohr/M +Moina/M +Moines/M +Moira/M +Moise/MS +Moiseyev/M +Moishe/M +Mojave/M +Moldavia/M +Moldavian/S +Moldova +Moliere +Molina/M +Moline/M +Moll/M +Mollee/M +Molli/M +Mollie/M +Molly/M +Molnar/M +Moloch/M +Molokai/M +Molotov/M +Moluccas +Mombasa/M +Mommy/M +Mon/SM +Mona/M +Monaco/M +Monah/M +Monash/M +Mondale/M +Monday/MS +Mondrian/M +Monegasque/SM +Monera/M +Monet/M +Monfort/M +Mongol/SM +Mongolia/M +Mongolian/S +Mongolic/M +Mongoloid/S +Monica/M +Monika/M +Monique/M +Monk/M +Monmouth/M +Monongahela/M +Monro/M +Monroe/M +Monrovia/M +Monsanto/M +Monsignor/MS +Monsignori +Mont/M +Montague/M +Montaigne/M +Montana/M +Montanan/MS +Montcalm/M +Montclair/M +Monte/M +Montenegrin +Montenegro/M +Monterey/M +Monterrey/M +Montesquieu/M +Montessori/M +Monteverdi/M +Montevideo/M +Montezuma +Montgomery/M +Monti/M +Monticello/M +Montmartre/M +Montoya/M +Montpelier/M +Montrachet/M +Montreal/M +Montserrat/M +Monty/M +Moody/M +Moog +Moon/M +Mooney/M +Moor/MS +Moore/M +Moorish +Mora/M +Morales/M +Moran/M +Moravia/M +Moravian +Mord/M +Mordecai/M +Mordred/M +Mordy/M +More/M +Moreen/M +Morehouse/M +Moreland/M +Morena/M +Moreno/M +Morey/M +Morgan/MS +Morgana/M +Morganica/M +Morganne/M +Morgen/M +Morgun/M +Moria/M +Moriarty/M +Morie/M +Morin/M +Morison/M +Morissa/M +Morita/M +Moritz/M +Morlee/M +Morley/M +Morly/M +Mormon/SM +Mormonism/MS +Morna/M +Moro/M +Moroccan/S +Morocco/M +Moroni/M +Morpheus/M +Morrie/M +Morris/M +Morrison/M +Morristown/M +Morrow/M +Morry/M +Morse/M +Mort/MN +Morten/M +Mortie/M +Mortimer/M +Morton/M +Morty/M +Mosaic +Moscone/M +Moscow/M +Mose/MSR +Moseley/M +Moselle/M +Moser/M +Moshe/M +Moslem's +Mosley/M +Moss/M +Mossberg/M +Mosul/M +Motorola/M +Motown/M +Mott/M +Mount/M +Mountbatten/M +Mountie/SM +Moussorgsky/M +Mouthe/M +Mouton/M +Mowgli/M +Moyer/M +Moyna/M +Moyra/M +Mozambican/S +Mozambique/M +Mozart/M +Mozelle/M +Mozes/M +Mozilla/M +Mr/M +Mrs +Ms/S +Msgr/M +Mt/M +Muawiya/M +Mubarak/M +Mueller/M +Muenster +Muffin/M +Mufi/M +Mufinella/M +Mugabe/M +Muhammad/M +Muhammadan/SM +Muhammadanism/S +Muir/M +Muire/M +Mukden/M +Mulder/M +Mullen/M +Muller/M +Mulligan/M +Mullikan/M +Mullins +Multan/M +Multibus/M +Multics/M +Mumford/M +Munch/M +Muncie/M +Mundt/M +Munich/M +Munmro/M +Munoz/M +Munro/M +Munroe/M +Munsey/M +Munson/M +Munster/MS +Muong/M +Muppet/M +Murasaki/M +Murat/M +Murchison/M +Murcia/M +Murdoch/M +Murdock/M +Mureil/M +Murial/M +Muriel/M +Murielle/M +Murillo/M +Murmansk/M +Murphy/M +Murray/M +Murrow/M +Murrumbidgee/M +Murry/M +Murvyn/M +Muscat/M +Muscovite/M +Muscovy/M +Muse/M +Musial/M +Muskegon/M +Muslim/MS +Mussolini/MS +Mussorgsky/M +Mutsuhito/M +Muzak/SM +Muzo/M +My/M +Myanmar +Myca/M +Mycah/M +Mycenae/M +Mycenaean +Mychal/M +Myer/MS +Mylar/S +Myles/M +Mylo/M +Mynheer/M +Myra/M +Myrah/M +Myranda/M +Myrdal/M +Myriam/M +Myrilla/M +Myrle/M +Myrlene/M +Myrna/M +Myron/M +Myrta/M +Myrtia/M +Myrtice/M +Myrtie/M +Myrtle/M +Myrvyn/M +Myrwyn/M +Mysore/M +Myst/M +Münchhausen/M +N +N'Djamena +N's +NAACP +NASA/MS +NASDAQ +NATO/SM +NB +NBA +NBC +NBS +NC +NCAA +NCC +NCO +NCR +ND +NE +NF +NFC +NFL +NFS +NH +NHL +NIH +NIMBY +NJ +NLRB +NM +NOAA +NORAD/M +NOW +NP +NRA +NS +NSF +NT +NV +NW +NWT +NY +NYC +NYSE +NZ +Na/M +NaCl/M +Nabisco/M +Nabokov/M +Nada/M +Nadean/M +Nadeen/M +Nader/M +Nadia/M +Nadine/M +Nadiya/M +Nady/M +Nadya/M +Nagasaki/M +Nagoya/M +Nagpur/M +Nagy/M +Nahuatl/SM +Nahum/M +Naipaul/M +Nair/M +Nairobi/M +Naismith/M +Nakamura/M +Nakayama/M +Nakoma/M +Nalani/M +Nam/M +Namath/M +Namibia/M +Namibian/S +Nan/M +Nana/M +Nanak/M +Nananne/M +Nance/M +Nancee/M +Nancey/M +Nanchang/M +Nanci/M +Nancie/M +Nancy/M +Nanete/M +Nanette/M +Nani/M +Nanice/M +Nanine/M +Nanjing +Nanking's +Nannette/M +Nanni/M +Nannie/M +Nanny/M +Nanon/M +Nanook/M +Nansen/M +Nantes/M +Nantucket/M +Naoma/M +Naomi/M +Nap/M +Naphtali/M +Napier/M +Naples/M +Napoleon/MS +Napoleonic +Nappie/M +Nappy/M +Nara/M +Narbonne/M +Narcissus/M +Nari/M +Nariko/M +Narmada/M +Narragansett/M +Nash/M +Nashua/M +Nashville/M +Nassau/M +Nasser/M +Nat/M +Nata/M +Natal/M +Natala/M +Natale/M +Natalee/M +Natalia/M +Natalie/M +Natalina/M +Nataline/M +Natalya/M +Nataniel/M +Natasha/M +Natassia/M +Natchez +Nate/XMN +Nathalia/M +Nathalie/M +Nathan/MS +Nathanael/M +Nathanial/M +Nathaniel/M +Nathanil/M +Natividad/M +Nativity/M +Natka/M +Natty/M +Naugahyde/S +Naur/M +Nauru/M +Navaho's +Navajo/S +Navajoes +Navarro/M +Navona/M +Navratilova/M +Navy/S +Nazarene/MS +Nazareth/M +Nazi/SM +Nazism/S +Nb/M +Nd/M +Ndjamena/M +Ne +NeWS/M +Neal/M +Neala/M +Neale/M +Neall/M +Nealon/M +Nealson/M +Nealy/M +Neanderthal/S +Neapolitan/SM +Neb/M +Nebr/M +Nebraska/M +Nebraskan/MS +Nebuchadnezzar/MS +Ned/M +Neda/M +Nedda/M +Neddie/M +Neddy/M +Nedi/M +Needham/M +Neel/M +Neely/M +Nefen/M +Nefertiti/M +Negev/M +Negress/MS +Negritude/S +Negro/M +Negroes +Negroid/S +Nehemiah/M +Nehru/M +Neil/SM +Neila/M +Neile/M +Neill/M +Neilla/M +Neille/M +Nelda/M +Nelia/M +Nelie/M +Nell/M +Nelle/M +Nelli/M +Nellie/M +Nelly/M +Nels/N +Nelsen/M +Nelson/M +Nembutal/M +Nemesis/M +Neogene +Neolithic/M +Nepal/M +Nepalese +Nepali/MS +Neptune/M +Nereid/M +Nerf/M +Nerissa/M +Nerita/M +Nero/M +Neron/M +Nert/M +Nerta/M +Nerte/M +Nerti/M +Nertie/M +Nerty/M +Neruda/M +Nessa/M +Nessi/M +Nessie/M +Nessy/M +Nesta/M +Nester/M +Nestle/M +Nestor/M +Nestorius/M +Netherlander/SM +Netherlands/M +Netscape/M +Netta/M +Netti/M +Nettie/M +Nettle/M +Netty/M +Netzahualcoyotl/M +Neumann/M +Nev/M +Neva/M +Nevada/M +Nevadan/S +Nevadian/S +Nevil/M +Nevile/M +Neville/M +Nevin/SM +Nevis/M +Nevsa/M +Nevsky/M +Newark/M +Newbury/M +Newburyport/M +Newcastle/M +Newell/M +Newfoundland/SRMZ +Newfoundlander/M +Newman/M +Newport/M +Newsweek/MY +Newsweekly/M +Newton/M +Newtonian +Nexis/M +Neysa/M +Ngaliema/M +Nguyen/M +Ni/M +Niagara/M +Nial/M +Niall/M +Niamey/M +Nibelung/M +Nicaean +Nicaragua/M +Nicaraguan/S +Niccolo/M +Nice/M +Nicene +Nichol/MS +Nicholas +Nichole/M +Nicholle/M +Nicholson/M +Nick/M +Nickey/M +Nicki/M +Nickie/M +Nicklaus/M +Nicko/M +Nickola/MS +Nickolai/M +Nickolaus/M +Nicky/M +Nico/M +Nicobar/M +Nicodemus/M +Nicol/M +Nicola/MS +Nicolai/MS +Nicole/M +Nicolea/M +Nicolette/M +Nicoli/MS +Nicolina/M +Nicoline/M +Nicolle/M +Nicosia/M +Niebuhr/M +Niel/MS +Niels/N +Nielsen/M +Nielson/M +Nietzsche/M +Nieves/M +Nigel/M +Niger/M +Nigeria/M +Nigerian/S +Nigerien +Nightingale/M +Nijinsky/M +Nikaniki/M +Nike/M +Niki/M +Nikita/M +Nikki/M +Nikkie/M +Nikko/M +Niko/MS +Nikola/MS +Nikolai/M +Nikolaos/M +Nikolaus/M +Nikolayev's +Nikoletta/M +Nikolia/M +Nikolos/M +Nikon/M +Nil/MS +Nile/SM +Nils/N +Nilsen/M +Nilson/M +Nilsson/M +Nimitz/M +Nimrod/MS +Nina/M +Ninetta/M +Ninette/M +Nineveh/M +Ninnetta/M +Ninnette/M +Ninon/M +Nintendo/M +Niobe/M +Nippon/M +Nipponese +Nirenberg/M +Nirvana/S +Nisei/MS +Nissa/M +Nissan/M +Nisse/M +Nissie/M +Nissy/M +Nita/M +Niven/M +Nixie/M +Nixon/M +Nkrumah/M +No/M +NoDoz/M +Noach/M +Noah/M +Noak/M +Noam/M +Noami/M +Nobe/M +Nobel/M +Nobelist/SM +Nobie/M +Noble/M +Noby/M +Noe/M +Noel/MS +Noelani/M +Noell/M +Noella/M +Noelle/M +Noellyn/M +Noelyn/M +Noemi/M +Nola/M +Nolan/M +Nolana/M +Noland/M +Nolie/M +Noll/M +Nollie/M +Nolly/M +Nome/M +Nomi/M +Nona/M +Nonah/M +Noni/M +Nonie/M +Nonna/M +Nonnah/M +Nora/M +Norah/M +Norbert/M +Norberto/M +Norbie/M +Norby/M +Nordhoff/M +Nordic/S +Nordstrom/M +Norean/M +Noreen/M +Norene/M +Norfolk/M +Norina/M +Norine/M +Norma/M +Norman/SM +Normand/M +Normandy/M +Normie/M +Normy/M +Norplant +Norri/SM +Norrie/M +Norristown/M +Norry/M +Norse +Norseman/M +Norsemen +North/M +Northampton/M +Northeast/SM +Northerner/M +Northfield/M +Northrop/M +Northrup/M +Norths +Northumberland/M +Northwest/MS +Norton/M +Norw/M +Norwalk/M +Norway/M +Norwegian/S +Norwich/M +Nosferatu/M +Nostradamus/M +Nostrand/M +Notre/M +Nottingham/M +Nouakchott/M +Noumea/M +Nov/M +Nova/M +Novak/M +Novelia/M +Novell/SM +November/SM +Novgorod/M +Novocain/S +Novocaine/M +Novokuznetsk/M +Novosibirsk/M +Nowell/M +Noyce/M +Noyes/M +Np +Nubia/M +Nubian/M +Nugent/M +Nukualofa +Numbers/M +Nunavut/M +Nunez/M +Nunki/M +Nuremberg/M +Nureyev/M +Nutrasweet/M +Nyasa/M +Nydia/M +Nye/M +Nyerere/M +Nyquist/M +Nyssa/M +O +O'Brien/M +O'Casey +O'Clock +O'Connell/M +O'Connor/M +O'Dell/M +O'Donnell/M +O'Dwyer/M +O'Er +O'Hara +O'Hare/M +O'Higgins +O'Keeffe +O'Leary/M +O'Neil +O'Neill +O'Shea/M +O'Sullivan/M +OAS +OB +OCR +OD +ODs +OE +OED +OEM/M +OEMS +OH +OHSA/M +OJ +OK/MDG +OKs +ON +OOo/M +OPEC +OR +OS/M +OSHA +OT +OTB +OTC +OTOH +Oahu/M +Oakland/M +Oakley/M +Oakmont/M +Oates/M +Oaxaca/M +Ob/MD +Obadiah/M +Obadias/M +Obed/M +Obediah/M +Oberlin/M +Oberon/M +Obidiah/M +Obie/M +Oby/M +Occam/M +Occident/SM +Occidental/S +Oceania/M +Oceanside/M +Oceanus/M +Ochoa/M +Oconomowoc/M +Oct/M +Octavia/M +Octavian/M +Octavio/M +Octavius/M +October/MS +Ode/MR +Odele/M +Odelia/M +Odelinda/M +Odell/M +Odella/M +Odelle/M +Oder/M +Oderberg/MS +Odessa/M +Odets/M +Odetta/M +Odette/M +Odey/M +Odie/M +Odilia/M +Odille/M +Odin/M +Odis/M +Odo/M +Odom/M +Ody/M +Odysseus/M +Odyssey/M +Oedipal/Y +Oedipus/M +Oersted/M +Ofelia/M +Ofella/M +Offenbach/M +Ofilia/M +Ogbomosho/M +Ogdan/M +Ogden/M +Ogdon/M +Ogilvy/M +Oglethorpe/M +Ohio/M +Ohioan/S +Oise/M +Ojibwa/SM +Okamoto/M +Okayama/M +Okeechobee/M +Okefenokee +Okhotsk/M +Okinawa/M +Okinawan/S +Okla/M +Oklahoma/M +Oklahoman/SM +Oktoberfest +Ola/M +Olaf/M +Olag/M +Olav/M +Oldenburg/M +Oldfield/M +Oldsmobile/M +Olduvai/M +Ole/MV +Oleg/M +Olen/M +Olenek/M +Olenka/M +Olenolin/M +Olga/M +Olia/M +Oligocene +Olimpia/M +Olin/M +Olive/MZR +Oliver/M +Olivero/M +Olivette/M +Olivetti/M +Olivia/M +Olivie/RM +Olivier/M +Oliviero/M +Oliy/M +Ollie/M +Olly/M +Olmec +Olmsted/M +Olsen/M +Olson/M +Olva/M +Olvan/M +Olwen/M +Olympe/M +Olympia/SM +Olympiad/MS +Olympian/S +Olympic/S +Olympie/M +Olympus/M +Omaha/SM +Oman/M +Omar/M +Omdurman/M +Omero/M +Omnipotent +Omsk/M +Onassis/M +Ondrea/M +Oneal/M +Onega/M +Onegin/M +Oneida/SM +Onfre/M +Onfroi/M +Onida/M +Ono/M +Onofredo/M +Onondaga/MS +Onsager/M +Ont/M +Ontarian/S +Ontario/M +Oona/M +Oort/M +Opal/M +Opalina/M +Opaline/M +Opel/M +OpenOffice.org/M +Ophelia/M +Ophelie/M +Ophiuchus/M +Oppenheimer/M +Oprah/M +Ora/M +Oralee/M +Oralia/M +Oralie/M +Oralla/M +Oralle/M +Oran/M +Orange/M +Oranjestad/M +Orazio/M +Orbadiah/M +Ordovician +Ore/NM +Oreg/M +Oregon/M +Oregonian/S +Orel/M +Orelee/M +Orelia/M +Orelie/M +Orella/M +Orelle/M +Oren/M +Oreo +Orestes +Oriana/M +Orient/SM +Oriental/S +Orin/M +Orinoco/M +Orion/M +Oriya/M +Orizaba/M +Orkney/M +Orlan/M +Orland/M +Orlando/M +Orleans +Orlick/M +Orlon/SM +Orly/M +Orono/M +Orpheus/M +Orphic +Orr/MN +Orran/M +Orren/M +Orrin/M +Orsa/M +Orsola/M +Orson/M +Ortega/M +Ortensia/M +Orthodox/S +Ortiz/M +Orton/M +Orv/M +Orval/M +Orville/M +Orwell/M +Orwellian +Os/M +Osage/SM +Osaka/M +Osbert/M +Osborn/M +Osborne/M +Osbourn/M +Osbourne/M +Oscar/SM +Osceola/M +Osgood/M +Oshawa/M +Oshkosh/M +Osiris/M +Oslo/M +Osman/M +Osmond/M +Osmund/M +Ossie/M +Ostrander/M +Ostrogoth/M +Ostwald/M +Osvaldo/M +Oswald/M +Oswell/M +Otes +Otha/M +Othelia/M +Othella/M +Othello/M +Othilia/M +Othilie/M +Otho/M +Otis/M +Ottawa/MS +Ottilie/M +Otto/M +Ottoman +Ouagadougou/M +Ouija/MS +Ovid/M +Owen/MS +Oxford/MS +Oxnard +Oxonian +Oxus/M +Oz/M +Ozark/SM +Ozymandias/M +Ozzie/M +Ozzy/M +P +P's +PA +PAC +PARC/M +PASCAL +PBS +PBX +PC/M +PCB +PCP +PCs +PD +PDP +PDQ +PDT +PE +PET +PFC +PG +PIN +PJ's +PLO +PM +PMS +PO +POW +PP +PPS +PR +PRC +PRO +PS +PST +PT +PTA +PTO +PVC +PW +PX +Pa/M +Pablo/M +Pablum/M +Pabst/M +Pace/M +Pacheco/M +Pacific/M +Packard/SM +Packston/M +Packwood/M +Paco/M +Pacorro/M +Padang/M +Paddie/M +Paddy/M +Padget/M +Padgett/M +Padilla/M +Padraic/M +Padraig/M +Padrewski/M +Padriac/M +Paganini/M +Page/M +Paglia/M +Pahlavi/M +Paige/M +Pail/M +Paine/M +Pakistan/M +Pakistani/S +Palatine +Palembang/M +Paleocene +Paleogene +Paleolithic +Paleozoic +Palermo/M +Palestine/M +Palestinian/S +Palestrina/M +Paley/M +Palisades/M +Pall/M +Palladio/M +Palm/MR +Palmer/M +Palmerston/M +Palmolive/M +Palmyra/M +Palo/M +Paloma/M +Palomar/M +Pam/M +Pamela/M +Pamelina/M +Pamella/M +Pamirs +Pammi/M +Pammie/M +Pammy/M +Pampers +Pan/M +Panama/MS +Panamanian/S +Panchito/M +Pancho/M +Pandora/M +Pangaea/M +Pankhurst/M +Panmunjom/M +Pansie/M +Pansy/M +Pantagruel/M +Pantaloon/M +Panza/M +Paola/M +Paoli/M +Paolina/M +Paolo/M +Papagena/M +Papageno/M +Pappas/M +Paquito/M +Paracelsus/M +Paraclete/M +Paradise/M +Paraguay/M +Paraguayan/S +Paramaribo/M +Paramus/M +Paraná +Parcheesi/M +Pareto/M +Paris/M +Parisian/SM +Park/RMS +Parke/M +Parker/M +Parkersburg/M +Parkhouse/M +Parkinson/M +Parkman +Parliament/MS +Parmesan/S +Parnassus/SM +Parnell/M +Parr/M +Parrish/M +Parrnell/M +Parry/M +Parsee's +Parsifal/M +Parsons/M +Parthenon/M +Parthia/M +Pasadena/M +Pascal/M +Pascale/M +Paso/M +Pasquale/M +Passaic/M +Passion/SM +Passover/MS +Pasternak/M +Pasteur/M +Pat/MN +Patagonia/M +Patagonian/S +Pate/M +Patel/M +Paten/M +Paterson/M +Patience/M +Patin/M +Patna/M +Paton/M +Patric/M +Patrica/M +Patrice/M +Patricia/M +Patricio/M +Patrick/M +Patrizia/M +Patrizio/M +Patrizius/M +Patsy/SM +Patten/M +Patterson/M +Patti/M +Pattie/M +Pattin/M +Patton/M +Patty/M +Paul/MG +Paula/M +Paule/M +Pauletta/M +Paulette/M +Pauli/M +Paulie/M +Paulina/M +Pauline +Pauling/M +Paulita/M +Paulo/M +Paulsen/M +Paulson/M +Paulus/M +Pauly/M +Pavarotti +Pavel/M +Pavia/M +Pavla/M +Pavlov/M +Pavlova/MS +Pavlovian +Pawnee/SM +Pawtucket/M +Paxon/M +Paxton/M +Payne/SM +Payson/M +Payton/M +Paz/M +Pb/M +Pd/M +Peabody/M +Peace/M +Peachtree/M +Peadar/M +Peale/M +Pearce/M +Pearl/M +Pearla/M +Pearle/M +Pearlie/M +Pearline/M +Pearson/M +Peary/M +Pebrook/M +Pechora/M +Peck/M +Peckinpah/M +Pecos/M +Peder/M +Pedro/M +Peel/M +Peg/M +Pegasus/MS +Pegeen/M +Peggi/M +Peggie/M +Peggy/M +Pei/M +Peiping/M +Peirce/M +Pekinese's +Peking/SM +Pekingese/SM +Pele/M +Pelee/M +Pelham/M +Peloponnese/M +Pembroke/M +Pen/M +Pena/M +Penderecki/M +Pendleton/M +Penelopa/M +Penelope/M +Penn/M +Penna +Penney/M +Penni/M +Pennie/M +Pennington/M +Pennsylvania/M +Pennsylvanian/S +Penny/M +Penrod/M +Pensacola/M +Pentagon/M +Pentateuch/M +Pentecost/SM +Pentecostal/S +Pentecostalism/S +Pentium/M +Peoria/M +Pepe/M +Pepi/M +Pepillo/M +Pepin/M +Pepita/M +Pepito/M +Pepsi/M +PepsiCo/M +Pepsico/M +Pepys/M +Pequot/M +Perceval/M +Percival/M +Percy/M +Perelman/M +Perez/M +Pergamon/M +Peri/M +Peria/M +Perice/M +Periclean +Pericles/M +Perilla/M +Perkin/SM +Perl/M +Perla/M +Perle/M +Perm/M +Permalloy/M +Permian +Pernell/M +Pernod/M +Peron/M +Perot/M +Perren/M +Perri/M +Perrine/M +Perry/MR +Perseid/M +Persephone/M +Perseus/M +Pershing/M +Persia/M +Persian/S +Persis/M +Perth/M +Peru/M +Peruvian/S +Peshawar/M +Pet/MRZ +Peta/M +Pete/M +Peter/M +Peters/N +Petersburg/M +Petersen/M +Peterson/M +Peterus/M +Petey/M +Petkiewicz/M +Petr/M +Petra/M +Petrarch/M +Petrina/M +Petronella/M +Petronia/M +Petronilla/M +Petronille/M +Pettibone/M +Petty/M +Petunia/M +Peugeot/M +Pewaukee/M +Peyter/M +Peyton/M +Pfc +Pfizer/M +Ph/M +PhD +Phaedra/M +Phaethon/M +Phaidra/M +Phanerozoic +Pharaoh/M +Pharaohs +Pharisaic +Pharisaical +Pharisee/SM +Phebe/M +Phedra/M +Phekda/M +Phelia/M +Phelps/M +Phidias/M +Phil/MY +Philadelphia/M +Philbert/M +Philco/M +Philip/M +Philipa/M +Philippa/M +Philippe/M +Philippians/M +Philippine/SM +Philis/M +Philistine/SM +Phillida/M +Phillie/M +Phillip/MS +Phillipa/M +Phillipe/M +Phillipp/M +Phillis/M +Philly/SM +Philomena/M +Phineas/M +Phip/M +Phipps/M +Phobos/M +Phoebe/M +Phoenicia/M +Phoenician/SM +Phoenix/M +Photostat/MS +Photostatted +Photostatting +Phylis/M +Phyllida/M +Phyllis/M +Phyllys/M +Phylys/M +Pia/M +Piaf/M +Piaget/M +Pianola/M +Picasso/M +Piccadilly/M +Pickering/M +Pickett/M +Pickford/M +Pickman/M +Pickwick/M +Pict/M +Piedmont/M +Pier/M +Pierce/M +Pierette/M +Pierre/M +Pierrette/M +Pierrot/M +Pierson/M +Pieter/M +Pietra/M +Pietrek/M +Pietro/M +Piggy/M +Pigmy's +Pike/M +Pilate/M +Pilcomayo/M +Pilgrim +Pillsbury/M +Pinatubo/M +Pincas/M +Pinchas/M +Pincus/M +Pindar/M +Pinehurst/M +Pinkerton/M +Pinocchio/M +Pinochet/M +Pinsky/M +Pinter/M +Pinyin +Piotr/M +Pip/MR +Piper/M +Pipestone/M +Pippa/M +Pippo/M +Pippy/M +Piraeus/M +Pirandello/M +Pisa/M +Pisces/M +Pisistratus/M +Pissaro/M +Pitcairn/M +Pitney/M +Pitt/SM +Pittman/M +Pittsburgh/ZM +Pittsfield/M +Pittston/M +Pius/M +Pizarro/M +Pkwy +Plainfield/M +Plainview/M +Planck/M +Plano +Plantagenet/M +Plasticine/M +Plath/M +Plato/M +Platonic +Platonism/M +Platonist +Platte/M +Platteville/M +Plautus/M +Playboy/M +Playtex/M +Pleiads +Pleistocene +Plexiglas/MS +Pliny/M +Pliocene/S +Plutarch/M +Pluto/M +Plymouth/M +Pm/M +Po/M +Pocahontas/M +Pocono/MS +Podgorica/M +Podunk/M +Poe/M +Pogo/M +Poincaré/M +Poindexter/M +Poisson/M +Pokemon/M +Pol/MY +Poland/M +Polanski/M +Polaris/M +Polaroid/SM +Pole/MS +Polish +Politburo/M +Polk/M +Pollard/M +Pollock/SM +Pollux/M +Polly/M +Pollyanna/M +Polo/M +Polyhymnia/M +Polynesia/M +Polynesian/S +Polyphemus/M +Pomerania/M +Pomeranian +Pomona/M +Pompadour/M +Pompeian/S +Pompeii/M +Pompey/M +Ponce/M +Ponchartrain/M +Pontchartrain/M +Pontiac/M +Pontianak/M +Pooh/M +Poole/M +Poona/M +Pope/SM +Popek/MS +Popeye/M +Popocatepetl/M +Popper/M +Poppins/M +Poppy/M +Popsicle/MS +Porfirio/M +Porrima/M +Porsche/M +Port/MR +Porte/M +Porter/M +Portia/M +Portie/M +Portland/M +Portsmouth/M +Portugal/M +Portuguese/M +Porty/M +Poseidon/M +Posner/M +Post/M +Potemkin/M +Potomac/M +Potsdam/M +Pottawatomie/M +Potter/M +Potts/M +Poughkeepsie/M +Poul/M +Pound/M +Poussin/MS +Powell/M +Powers +Powhatan/M +Poznan/M +Pr/MN +Pradesh/M +Prado/M +Praetorian +Prague/M +Praia +Prakrit/M +Pratchett/M +Pratt/M +Prattville/M +Pravda/M +Praxiteles/M +Preakness/M +Precambrian +Preminger/M +Pren/M +Prent/M +Prentice/MGD +Prenticed/M +Prenticing/M +Prentiss/M +Pres +Presbyterian/S +Presbyterianism/S +Prescott/M +Presley/M +Preston/M +Pretoria/M +Priam/M +Pribilof/M +Price/M +Priestley/M +Prime's +Prince/M +Princeton/M +Principe/M +Principia/M +Prinz/M +Pris +Prisca/M +Priscella/M +Priscilla/M +Prissie/M +Procrustean +Procrustes/M +Procyon/M +Prof +Prohibition/MS +Prokofieff/M +Prokofiev/M +Promethean +Prometheus/M +Proserpine/M +Protagoras/M +Proterozoic/M +Protestant/SM +Protestantism/MS +Proteus/M +Proudhon/M +Proust/M +Provencals +Provence/M +Provençal +Proverbs/M +Providence/SM +Provo/M +Proxmire/M +Prozac +Pru/M +Prudence/M +Prudential/M +Prudi/M +Prudy/M +Prue/M +Pruitt/M +Prussia/M +Prussian/S +Prut/M +Pryce/M +Psalms/M +Psalter/SM +Psyche/M +Pt/M +Ptah/M +Ptolemaic +Ptolemaists +Ptolemy/MS +Pu +Puccini/M +Puck/M +Puckett/M +Puebla/M +Pueblo/MS +Puerto/M +Puff/M +Puget/M +Pugh/M +Pulaski/SM +Pulitzer/SM +Pullman/MS +Punch/M +Punic +Punjab/M +Punjabi/M +Purcell/M +Purdue/M +Purim/SM +Purina/M +Puritan/SM +Puritanism/MS +Purus +Pusan/M +Pusey/M +Pushkin/M +Pushtu/M +Putin/M +Putnam/M +Putnem/M +Pvt/M +Pygmalion/M +Pygmy/SM +Pyhrric/M +Pyle/M +Pym/M +Pynchon/M +Pyongyang/M +Pyotr/M +Pyrenees +Pyrex/SM +Pyrrhic +Pythagoras/M +Pythagorean/S +Pythias +Python/M +Pétain/M +Pôrto/M +Q +Q's +QA +QB +QC +QED +QM +Qaddafi/M +Qantas/M +Qatar/M +Qingdao +Qiqihar/M +Qom/M +Quaalude/M +Quaker/SM +Quakeress/M +Quakerism/S +Quantico/M +Quasimodo/M +Quaternary +Quayle/M +Que/M +Quebec/M +Quechua/M +Queen/SM +Queenie/M +Queensland/M +Quent/M +Quentin/M +Querida/M +Quetzalcoatl/M +Quezon/M +Quill/M +Quillan/M +Quincey/M +Quincy/M +Quinlan/M +Quinn/M +Quint/M +Quinta/M +Quintana/M +Quintilian/M +Quintilla/M +Quintin/M +Quintina/M +Quinton/M +Quintus/M +Quirinal/M +Quisling/M +Quito/M +Quixote/M +Quixotism/M +Quonset +R's +R/G +RAF +RAM/S +RBI/S +RC +RCA +RCS +RD +RDA +REIT +REM/S +RF +RFC +RFD +RI +RIP +RISC +RMS +RN +RNA +ROFL +ROM +ROTC +RP +RPM +RR +RSFSR +RSI +RSV +RSVP +RSX +RTFM +RV +RVs +Ra/M +Rab/M +Rabat/M +Rabbi/M +Rabelais/M +Rabelaisian +Rabi/M +Rabin/M +Rachael/M +Rachel/M +Rachele/M +Rachelle/M +Rachmaninoff/M +Racine/M +Rad/M +Radcliffe/M +Raddie/M +Raddy/M +Rae/M +Raeann/M +Raf/M +Rafa/M +Rafael/M +Rafaela/M +Rafaelia/M +Rafaelita/M +Rafaellle/M +Rafaello/M +Rafe/M +Raff/M +Raffaello/M +Raffarty/M +Rafferty/M +Rafi/M +Ragnar/M +Ragnarök +Rahal/M +Rahel/M +Raimondo/M +Raimund/M +Raimundo/M +Raina/M +Raine/MR +Rainer/M +Rainier/M +Rajive/M +Rakel/M +Raleigh/M +Ralf/M +Ralina/M +Ralph/M +Ralston/M +Ram/M +Rama/M +Ramada/M +Ramadan/SM +Ramakrishna/M +Raman/M +Ramayana/M +Rambo/M +Ramirez/M +Ramiro/M +Ramo/MS +Ramon/M +Ramona/M +Ramonda/M +Ramsay/M +Ramses/M +Ramsey/M +Rana/M +Rance/M +Rancell/M +Rand/M +Randa/M +Randal/M +Randall/M +Randee/M +Randell/M +Randene/M +Randi/M +Randie/M +Randolf/M +Randolph/M +Randy/M +Ranee/M +Rangoon/M +Rani/MR +Rania/M +Ranice/M +Ranier/M +Ranique/M +Rankin/M +Rankine/M +Ranna/M +Ransell/M +Ransom/M +Raoul/M +Raphael/M +Raphaela/M +Rapunzel/M +Raquel/M +Raquela/M +Rasalgethi/M +Rasalhague/M +Rasia/M +Rasla/M +Rasmussen/M +Rasputin/M +Rastaban/M +Rastafarian/M +Rastus/M +Ratfor/M +Rather/M +Ratliff/M +Raul/M +Ravel/M +Raven/M +Ravi/M +Ravid/M +Raviv/M +Rawalpindi/M +Rawley/M +Rawlings/M +Rawlins/M +Rawlinson/M +Rawson/M +Ray/M +Rayburn/M +Raychel/M +Raye/M +Rayleigh/M +Raymond/M +Raymondville/M +Raymund/M +Raymundo/M +Rayna/M +Raynard/M +Raynell/M +Rayner/M +Raynor/M +Rayshell/M +Raytheon/M +Rb +Rd/M +Re/M +Rea/M +Read/GM +Reade/M +Reading/M +Reagan/M +Reagen/M +Realtor/S +Reamonn/M +Reasoner/M +Reba/M +Rebbecca/M +Rebe/M +Rebeca/M +Rebecca's +Rebecka/M +Rebeka/M +Rebekah/M +Rebekkah/M +Recife/M +Reconstruction/M +Redd/M +Redeemer/M +Redford/M +Redgrave/M +Redhook/M +Redmond/M +Redondo/M +Redstone/M +Ree/MDS +Reeba/M +Reebok/M +Reece/M +Reed/M +Reedville/M +Reena/M +Reese/M +Reeta/M +Reeva/M +Reeves +Refugio/M +Reg/MN +Regan/M +Regen/M +Reggi/MS +Reggie/M +Reggy/M +Regina/M +Reginae +Reginald/M +Reginauld/M +Regine/M +Regis/M +Regor/M +Regulus/M +Rehnquist +Reich/M +Reichenberg/M +Reichstag's +Reichstags +Reid/MR +Reidar/M +Reider/M +Reiko/M +Reilly/M +Reina/M +Reinald/M +Reinaldo/MS +Reine/M +Reinhard/M +Reinhardt/M +Reinhold/M +Reinold/M +Reinwald/M +Rem/M +Remarque/M +Rembrandt/M +Remington/M +Remus/M +Remy/M +Rena/M +Renado/M +Renae/M +Renaissance/SM +Renaldo/M +Renard/M +Renascence/SM +Renata/M +Renate/M +Renato/M +Renaud/M +Renault/MS +Rene/M +Renee/M +Renell/M +Renelle/M +Renie/M +Rennie/M +Reno/M +Renoir/M +Rensselaer/M +Renville/M +Rep/M +Representative/S +Republican/S +Republicanism/S +Requiem/MS +Resistance/SM +Restoration/M +Reta/M +Retha/M +Reub/NM +Reube/M +Reuben/M +Reunion/M +Reuters +Reuther/M +Reuven/M +Rev/M +Reva/M +Revelation/MS +Revere/M +Reverend +Revkah/M +Revlon/M +Rex/M +Rey/M +Reyes +Reykjavik/M +Reyna/M +Reynaldo/M +Reynard/M +Reynold/SM +Rf +Rh/M +Rhea/M +Rheba/M +Rhee/M +Rheims/M +Rheinholdt/M +Rhenish +Rheta/M +Rhett/M +Rhetta/M +Rhiamon/M +Rhianna/M +Rhiannon/M +Rhianon/M +Rhine/M +Rhineland/RM +Rhinelander/M +Rhoda/M +Rhodes +Rhodesia/M +Rhodesian/S +Rhodia/M +Rhodie/M +Rhody/M +Rhona/M +Rhonda/M +Rhone +Rhys/M +Riane/M +Riannon/M +Rianon/M +Ribbentrop/M +Ric/M +Rica/M +Rican/SM +Ricard/M +Ricardo/M +Ricca/M +Riccardo/M +Rice/M +Rich/M +Richard/MS +Richardo/M +Richardson/M +Richart/M +Richelieu/M +Richey/M +Richfield/M +Richie/M +Richland/M +Richmond/M +Richmound/M +Richter/M +Richthofen/M +Richy/M +Rici/M +Rick/M +Rickard/M +Rickenbacker/M +Rickenbaugh/M +Rickert/M +Rickey/M +Ricki/M +Rickie/M +Rickover/M +Ricky/M +Rico/M +Ricoriki/M +Riddle/M +Ride/M +Ridgefield/M +Ridgway/M +Riemann/M +Riesling/SM +Riga/M +Rigel/M +Riggs/M +Right/S +Rigoberto/M +Rigoletto/M +Rik/M +Riki/M +Rikki/M +Riley/M +Rilke/M +Rimbaud/M +Rina/M +Rinaldo/M +Rinehart/M +Ring/M +Ringling/M +Ringo/M +Rio/MS +Riobard/M +Riordan/M +Rip/M +Ripley/M +Risa/M +Rita/M +Ritalin +Ritchie/M +Ritter/M +Ritz/M +Riva/MS +Rivalee/M +Rivera/M +Rivers +Riverside/M +Riverview/M +Rivi/M +Riviera/MS +Rivkah/M +Rivy/M +Riyadh/M +Rn/M +Roach/M +Roana/M +Roanna/M +Roanne/M +Roanoke/M +Roarke/M +Rob/MZ +Robb/M +Robbert/M +Robbi/M +Robbie/M +Robbin/MS +Robby/M +Robbyn/M +Robena/M +Robenia/M +Robers/M +Roberson/M +Robert/MS +Roberta/M +Roberto/M +Robertson/SM +Robeson/M +Robespierre/M +Robin/M +Robina/M +Robinet/M +Robinett/M +Robinetta/M +Robinette/M +Robinia/M +Robinson/M +Robinsonville/M +Robles/M +Robson/M +Robt/M +Roby/M +Robyn/M +Rocco/M +Roch/M +Rocha/M +Rochambeau/M +Roche/M +Rochell/M +Rochella/M +Rochelle/M +Rochester/M +Rochette/M +Rock/M +Rockaway/MS +Rockefeller/M +Rockey/M +Rockford/M +Rockie/M +Rockland/M +Rockne/M +Rockville/M +Rockwell/M +Rocky/SM +Rod/M +Roda/M +Rodd/M +Roddenberry/M +Roddie/M +Roddy/M +Roderic/M +Roderich/M +Roderick/M +Roderigo/M +Rodge/ZMR +Rodger/M +Rodi/M +Rodie/M +Rodin/M +Rodina/M +Rodney/M +Rodolfo/M +Rodolph/M +Rodolphe/M +Rodrick/M +Rodrigo/M +Rodriguez/M +Rodrique/M +Rodriquez/M +Roentgen's +Rog/MRZ +Rogelio/M +Roger/M +Rogerio/M +Roget/M +Roi/SM +Rojas/M +Roland/M +Rolando/M +Roldan/M +Roley/M +Rolf/M +Rolfe/M +Rolland/M +Rollerblade/S +Rollie/M +Rollin/SM +Rollo/M +Rolodex +Rolph/M +Rolvaag/M +Rom/SM +Roma/M +Romain/M +Roman/SM +Romanesque/S +Romania/M +Romanian/SM +Romano/MS +Romanov/M +Romans/M +Romansh/M +Romanticism/S +Romany/SM +Rome/SM +Romeo/MS +Romero/M +Rommel/M +Romney/M +Romola/M +Romona/M +Romonda/M +Romulus/M +Romy/M +Ron/M +Rona/M +Ronald/M +Ronalda/M +Ronda/M +Ronica/M +Ronna/M +Ronni/M +Ronnica/M +Ronnie/M +Ronny/M +Ronstadt/M +Rontgen +Roobbie/M +Rooney/M +Roosevelt/M +Rooseveltian +Root/M +Roquefort/MS +Roquemore/M +Rora/M +Rori/M +Rorie/M +Rorke/M +Rorschach +Rory/M +Ros/N +Rosa/M +Rosabel/M +Rosabella/M +Rosabelle/M +Rosaleen/M +Rosales/M +Rosalia/M +Rosalie/M +Rosalind/M +Rosalinda/M +Rosalinde/M +Rosaline/M +Rosalyn/M +Rosalynd/M +Rosamond/M +Rosamund/M +Rosana/M +Rosanna/M +Rosanne/M +Rosario/M +Rosco/M +Roscoe/M +Rose/M +Roseann/M +Roseanna/M +Roseanne/M +Roseau +Rosecrans/M +Roseland/M +Roselia/M +Roselin/M +Roseline/M +Rosella/M +Roselle/M +Rosemaria/M +Rosemarie/M +Rosemary/M +Rosemonde/M +Rosen/M +Rosenberg/M +Rosenblum/M +Rosendo/M +Rosene/M +Rosenthal/M +Rosenzweig/M +Rosetta/M +Rosette/M +Roshelle/M +Rosicrucian/M +Rosie/M +Rosina/M +Rosita/M +Roslyn/M +Rosmunda/M +Ross +Rossetti/M +Rossi/M +Rossie/M +Rossini/M +Rossy/M +Rostand/M +Rostov/M +Roswell/M +Rosy/M +Rotarian/SM +Roth/M +Rothschild/M +Rotterdam/M +Rouault/M +Rourke/M +Rousseau/M +Rouvin/M +Rover/M +Row/MN +Rowan/M +Rowe/M +Rowen/M +Rowena/M +Rowland/M +Rowley/M +Rowney/M +Roxana/M +Roxane/M +Roxanna/M +Roxanne/M +Roxi/M +Roxie/M +Roxine/M +Roxy/M +Roy/M +Royal/M +Royall/M +Royce/M +Roz/M +Rozalie/M +Rozalin/M +Rozamond/M +Rozanna/M +Rozanne/M +Roze/M +Rozele/M +Rozella/M +Rozelle/M +Rozina/M +Rriocard/M +Rte +Ru/MH +Rubaiyat/M +Rube/M +Ruben/MS +Rubetta/M +Rubi/M +Rubia/M +Rubicon/SM +Rubie/M +Rubik/M +Rubin/M +Rubina/M +Rubinstein/M +Ruby/M +Ruchbah/M +Rudd/M +Ruddie/M +Ruddy/M +Rudie/M +Rudiger/M +Rudolf/M +Rudolfo/M +Rudolph/M +Rudy/M +Rudyard/M +Rufe/M +Rufus/M +Rugby's +Ruggiero/M +Ruhr/M +Ruiz/M +Rumania's +Rumanian's +Rumford/M +Rummel/M +Rumpelstiltskin/M +Runge/M +Runnymede/M +Runyon/M +Rupert/M +Ruperta/M +Ruperto/M +Ruppert/M +Ruprecht/M +Rurik/M +Rush/M +Rushdie/M +Rushmore/M +Ruskin/M +Russ/S +Russel/M +Russell/M +Russia/M +Russian/SM +Russo/M +Rustbelt/M +Rustie/M +Rustin/M +Rusty/M +Rutger/SM +Ruth/M +Ruthann/M +Ruthanne/M +Ruthe/M +Rutherford/M +Ruthi/M +Ruthie/M +Ruthy/M +Rutland/M +Rutledge/M +Rutter/M +Ruttger/M +Ruy/M +Rwanda/SM +Rwandan/S +Rwy/M +Rx/M +Ry/M +Ryan/M +Ryann/M +Rycca/M +Rydberg/M +Ryder/M +Ryley/M +Ryon/M +Ryukyu/M +Ryun/M +S +S's +SA +SAC +SALT +SAM +SASE +SAT +SBA +SC +SCCS +SCSI +SD +SDI +SE +SEATO +SEC +SF +SIDS +SIGGRAPH/M +SIMD +SIMULA/M +SJ +SK +SLR +SMSA/MS +SMTP +SO +SOP +SOS +SPARC/M +SPARCstation/M +SPCA +SPF +SPSS +SRO +SS +SSA +SSE +SSS +SST +SSW +ST +STD +STOL +SUV +SW +SWAK +SWAT +Saab/M +Saar/M +Saba/M +Sabbath/M +Sabbaths +Sabik/M +Sabin/M +Sabina/M +Sabine/M +Sabra/M +Sabrina/M +Sacajawea/M +Sacco/M +Sacha/M +Sachs/M +Sacramento/M +Sada/M +Sadat/M +Saddam/M +Sadducee/M +Sade/M +Sadella/M +Sadie/M +Sadr/M +Sadye/M +Sagan/M +Saginaw/M +Sagittarius/MS +Sahara/M +Saharan/M +Sahel +Saidee/M +Saigon/M +Saiph/M +Sakai/M +Sakhalin/M +Sakharov/M +Saki/M +Sal/MY +Saladin/M +Salado/M +Salaidh/M +Salas/M +Salazar/M +Saleem/M +Salem/M +Salerno/M +Salim/M +Salina/MS +Salinger/M +Salisbury/M +Salish/M +Salk/M +Salle/M +Sallee/M +Salli/M +Sallie/M +Sallust/M +Sally/M +Sallyann/M +Sallyanne/M +Salmon/M +Saloma/M +Salome/M +Salomi/M +Salomo/M +Salomon/M +Salomone/M +Salonika/M +Salton/M +Salvador/M +Salvadoran/S +Salvadorian/S +Salvatore/M +Salvidor/M +Salween/M +Salyut/M +Salz/M +Sam/M +Samantha/M +Samara/M +Samaria/M +Samaritan/MS +Samarkand/M +Sammie/M +Sammy/M +Samoa +Samoan/S +Samoset/M +Samoyed/M +Sampson/M +Samson/M +Samsonite/M +Samuel/SM +Samuele/M +Samuelson/M +San'a +San/M +Sana/M +Sanborn/M +Sanchez/M +Sancho/M +Sand/MRZ +Sandburg/M +Sande/M +Sander/M +Sanderling/M +Sanderson/M +Sandi/M +Sandia/M +Sandie/M +Sandinista +Sandor/M +Sandoval/M +Sandra/M +Sandro/M +Sandusky/M +Sandy/M +Sandye/M +Sanford/M +Sanforized +Sang/RM +Sanger/M +Sanhedrin/M +Sankara/M +Sanskrit/M +Sanskritic +Sanskritize/M +Sanson/M +Sansone/M +Santa/M +Santana/M +Santayana/M +Santeria +Santiago/M +Santo/MS +Sapphira/M +Sapphire/M +Sappho/M +Sapporo/M +Sara/M +Saraann/M +Saracen/MS +Saragossa/M +Sarah/M +Sarajane/M +Sarajevo/M +Saran/M +Sarasota/M +Saratoga/M +Saratov/M +Sarawak/M +Sardinia/M +Saree/M +Sarena/M +Sarene/M +Sarette/M +Sargasso/M +Sarge/M +Sargent/M +Sargon/M +Sari/M +Sarina/M +Sarine/M +Sarita/M +Sarnoff/M +Saroyan/M +Sarto/M +Sartre/M +Sascha/M +Sasha/M +Sashenka/M +Sask/M +Saskatchewan/M +Saskatoon/M +Sassoon/M +Sat/M +Satan/M +Satanism/M +Satanist/M +Saturday/MS +Saturn/M +Saturnalia/M +Satyanarayanan/M +Saud/M +Saudi/S +Saudra/M +Saukville/M +Saul/M +Sault/M +Sauncho/M +Saunder/SM +Saunderson/M +Saundra/M +Saussure/M +Sauternes/M +Sauveur/M +Savage/M +Savannah/M +Savina/M +Savior/M +Saviour/M +Savonarola/M +Savoy/M +Savoyard/M +Saw/M +Sawyer/M +Sawyere/M +Sax/M +Saxe/M +Saxon/SM +Saxony/M +Saxton/M +Say/ZMR +Sayer/M +Sayre/MS +Sb/M +Sc/M +Scala/M +Scan +Scandinavia/M +Scandinavian/S +Scaramouch/M +Scarborough/M +Scarface/M +Scarlatti/M +Scarlet/M +Scarlett/M +Schaefer/M +Schaeffer/M +Schafer/M +Schaffner/M +Schantz/M +Schapiro/M +Scheat/M +Schedar/M +Scheherazade/M +Scheherezade/M +Schelling/M +Schenectady/M +Schick/M +Schiller/M +Schlesinger/M +Schliemann/M +Schlitz/M +Schloss/M +Schmidt/M +Schmitt/M +Schnabel/M +Schneider/M +Schoenberg/M +Schofield/M +Schopenhauer/M +Schottky/M +Schrieffer/M +Schroeder/M +Schroedinger/M +Schrödinger/M +Schubert/M +Schultz/M +Schulz/M +Schumacher/M +Schuman/M +Schumann/M +Schuster/M +Schuyler/M +Schuylkill/M +Schwab/M +Schwartz/M +Schwartzkopf/M +Schwarzenegger/M +Schweitzer/M +Schweppes/M +Schwinger/M +Schwinn/M +Scientology/M +Scipio/M +Scopes/M +Scorpio/SM +Scorpius/M +Scorsese/M +Scot/MS +Scotch/S +Scotchgard/M +Scotchman/M +Scotchmen +Scotchwoman +Scotchwomen +Scotia/M +Scotian/M +Scotland/M +Scotsman/M +Scotsmen +Scotswoman +Scotswomen +Scott/M +Scotti/M +Scottie/SM +Scottish +Scottsdale/M +Scotty's +Scout's +Scrabble/SM +Scranton/M +Scriabin/M +Scribner/MS +Scripps/M +Scripture/MS +Scrooge/MS +Scruggs/M +Scud/M +Sculley/M +Scylla/M +Scythia/M +Se/H +Seaborg/M +Seabrook/M +Seagate/M +Seagram/M +Seamus/M +Sean/M +Seana/M +Seaquarium/M +Sears/M +Seattle/M +Sebastian/M +Sebastiano/M +Sebastien/M +Seconal +Seder/SM +Sedgwick/M +See/M +Seebeck/M +Seeley/M +Segovia/M +Segre/M +Segundo/M +Seidel/M +Seiko/M +Seine/M +Seinfeld/M +Seka/M +Sela/M +Selassie/M +Selby/M +Selectric/M +Selena/M +Selene/M +Selestina/M +Seleucid/M +Seleucus/M +Selfridge/M +Selia/M +Selie/M +Selig/M +Selim/M +Selina/M +Selinda/M +Seline/M +Seljuk/M +Selkirk/M +Sella/M +Selle/ZM +Sellers/M +Selma/M +Selznick/M +Semarang/M +Seminole/SM +Semiramis/M +Semite/SM +Semitic/MS +Semtex +Sen +Sena/M +Senate/MS +Sendai/M +Seneca/MS +Senegal/M +Senegalese +Senior/S +Sennacherib/M +Sennett/M +Sensurround/M +Seoul/M +Sephardi/M +Sephira/M +Sepoy/M +Sept/M +September/MS +Septuagint/MS +Sequoia/M +Sequoya/M +Serafin/M +Serb/MS +Serbia/M +Serbian/S +Serbo/M +Serena/M +Serene/M +Serengeti/M +Serge/M +Sergeant/M +Sergei/M +Sergent/M +Sergio/M +Serpens/M +Serra/M +Serrano/M +Set/M +Seth/M +Seton/M +Seumas/M +Seurat/M +Seuss/M +Sevastopol/M +Severn/M +Severus/M +Seville/M +Seward/M +Sextans/M +Sexton/M +Seychelles +Seyfert +Seymour/M +Señora/M +Sgt +Shackleton/M +Shadow/M +Shae/M +Shafer/M +Shaffer/M +Shaina/M +Shaine/M +Shaker/S +Shakespeare/M +Shakespearean/S +Shakespearian +Shalna/M +Shalne/M +Shalom/M +Shamus/M +Shana/M +Shanan/M +Shanda/M +Shandee/M +Shandeigh/M +Shandie/M +Shandra/M +Shandy/M +Shane/M +Shanghai/GM +Shanghaiing/M +Shani/M +Shanie/M +Shanna/M +Shannah/M +Shannan/M +Shannen/M +Shannon/M +Shanon/M +Shanta/M +Shantee/M +Shantung/M +Shapiro/M +Shara/M +Sharai/M +Shari'a +Shari/M +Sharia/M +Sharity/M +Sharl/M +Sharla/M +Sharleen/M +Sharlene/M +Sharline/M +Sharon/M +Sharona/M +Sharp/M +Sharpe/M +Sharron/M +Sharyl/M +Shasta/M +Shaughn/M +Shaula/M +Shaun/M +Shauna/M +Shavian +Shavuot/M +Shaw/M +Shawano/M +Shawn/M +Shawna/M +Shawnee/SM +Shay/M +Shayla/M +Shaylah/M +Shaylyn/M +Shaylynn/M +Shayna/M +Shayne/M +Shcharansky/M +Shea/M +Sheba/M +Shebeli/M +Sheboygan/M +Shedir/M +Sheela/M +Sheelagh/M +Sheelah/M +Sheena/M +Sheeree/M +Sheetrock +Sheff/M +Sheffie/M +Sheffield/RMZ +Sheffielder/M +Sheffy/M +Sheila/M +Sheilah/M +Shel/MY +Shela/M +Shelagh/M +Shelba/M +Shelbi/M +Shelby/M +Shelden/M +Sheldon/M +Shelia/M +Shell/M +Shelley/M +Shelli/M +Shellie/M +Shelly/M +Shelton/M +Shem/M +Shena/M +Shenandoah/M +Shenyang/M +Sheol/M +Shep/M +Shepard/M +Shepherd/M +Sheppard/M +Shepperd/M +Sher/M +Sheratan/M +Sheraton/M +Sheree/M +Sheri/M +Sheridan/M +Sherie/M +Sherill/M +Sherilyn/M +Sherline/M +Sherlock/M +Sherlocke/M +Sherm/M +Sherman/M +Shermie/M +Shermy/M +Sherpa/SM +Sherri/M +Sherrie/M +Sherry/M +Sherwin/M +Sherwood/M +Sherwynd/M +Sherye/M +Sheryl/M +Shetland/S +Shevardnadze/M +Shi'ite +Shields/M +Shiite/SM +Shijiazhuang +Shikoku/M +Shillong/M +Shiloh/M +Shina/M +Shinto/MS +Shintoism/S +Shintoist/MS +Shir/M +Shiraz/M +Shirl/M +Shirlee/M +Shirleen/M +Shirlene/M +Shirley/M +Shirline/M +Shiva/M +Shmuel/M +Shockley/M +Shoji/M +Sholom/M +Shorewood/M +Short/M +Shorthorn/M +Shoshana/M +Shoshanna/M +Shoshone/SM +Shostakovitch/M +Shreveport/M +Shropshire/M +Shu/M +Shulman/M +Shurlock/M +Shurlocke/M +Shurwood/M +Shuttleworth +Shuttleworth's +Shylock/M +Shylockian/M +Si/M +Siam/M +Siamese/M +Sian's +Siana/M +Sianna/M +Sib/M +Sibbie/M +Sibby/M +Sibeal/M +Sibel/M +Sibelius/M +Sibella/M +Sibelle/M +Siberia/M +Siberian/S +Sibilla/M +Sibley/M +Sibyl/M +Sibylla/M +Sibylle/M +Sicilian/S +Siciliana/M +Sicily/M +Sid/M +Sidnee/M +Sidney/M +Sidoney/M +Sidonia/M +Sidonnie/M +Siegel/M +Siegfried/M +Sieglinda/M +Siegmund/M +Siemens/M +Siena/M +Sierpinski/M +Siffre/M +Sig/M +Sigfrid/M +Sigfried/M +Sigismond/M +Sigismondo/M +Sigismund/M +Sigismundo/M +Sigmund/M +Signor/M +Signora/M +Sigrid/M +Sigurd/M +Sigvard/M +Sihanouk/M +Sikh/MS +Sikhism/MS +Sikhs +Sikkim/M +Sikkimese +Sikorsky/M +Silas/M +Sile/M +Sileas/M +Silesia/M +Silurian/S +Silva/M +Silvain/M +Silvan/M +Silvana/M +Silvano/M +Silvanus/M +Silverman/M +Silverstein/M +Silvester/M +Silvia/M +Silvie/M +Silvio/M +Sim/MS +Simenon/M +Simeon/M +Simla/M +Simmonds/M +Simmons/M +Simmonsville/M +Simms/M +Simon/M +Simona/M +Simone/M +Simonette/M +Simonne/M +Simpson/M +Simula/M +Sinai/M +Sinatra/M +Sinclair/M +Sinclare/M +Sindbad/M +Sindee/M +Sindhi/M +Singapore/M +Singaporean/S +Singborg/M +Singer/M +Singleton/M +Sinhalese/M +Sinkiang/M +Siobhan/M +Sioux/M +Siouxie/M +Sir/MS +Sirius/M +Sisely/M +Sisile/M +Sissie/M +Sissy/M +Sistine +Sisyphean +Sisyphus/M +Siusan/M +Siva/M +Siward/M +Sjaelland/M +Skell/M +Skelly/M +Skinner/M +Skip/M +Skipp/RM +Skipper/M +Skippie/M +Skippy/M +Skipton/M +Skopje/M +Sky/M +Skye/M +Skylab/M +Skylar/M +Skyler/M +Slade/M +Slater/M +Slav/MS +Slavic/M +Slavonic/M +Slesinger/M +Sloan/M +Sloane/M +Slocum/M +Slovak/S +Slovakia/M +Slovakian/S +Slovene/S +Slovenia/M +Slovenian/S +Sly/M +Sm/M +Small/M +Smallwood/M +Smetana/M +Smirnoff/M +Smith/M +Smithfield/M +Smithson/M +Smithsonian/M +Smithtown/M +Smitty/M +Smokey/M +Smolensk/M +Smollett/M +Smucker/M +Smuts/M +Smyrna/M +Sn/M +Snake +Snead/M +Sneed/M +Snell/M +Snider/M +Snodgrass/M +Snoopy/M +Snow/M +Snowbelt/SM +Snyder/M +Soc +Socorro/M +Socrates/M +Socratic/S +Soddy/M +Sodom/M +Sofia/M +Sofie/M +Soho/M +Sol/MY +Solis/M +Sollie/M +Solly/M +Solomon/SM +Solon/M +Soloviev/M +Solzhenitsyn/M +Somali/MS +Somalia/M +Somalian/S +Somerset/M +Somerville/M +Somme/M +Somoza/M +Son/M +Sondheim/M +Sondra/M +Sonenberg/M +Songhai/M +Songhua/M +Sonia/M +Sonja/M +Sonni/M +Sonnie/M +Sonnnie/M +Sonny/M +Sonoma/M +Sonora/M +Sontag/M +Sony/M +Sonya/M +Sophey/M +Sophi/M +Sophia/SM +Sophie/M +Sophoclean +Sophocles/M +Sophronia/M +Sopwith/M +Sorbonne/M +Sorcha/M +Sorensen/M +Sorenson/M +Sorrentine/M +Sosa/M +Sosanna/M +Soto/M +Souphanouvong/M +Sousa/M +South/M +Southampton/M +Southeast/MS +Southerner/MS +Southey/M +Southfield/M +Souths +Southwest/MS +Soviet/S +Soweto/M +Soyinka/M +Soyuz/M +Sp/M +Spaatz/M +Spacewar/M +Spackle +Spafford/M +Spahn/M +Spain/M +Spalding/M +Spam/M +Span +Spanglish/S +Spaniard/SM +Spanish/M +Sparkman/M +Sparks +Sparta/M +Spartacus/M +Spartan/S +Speaker's +Spears +Spence/RM +Spencer/M +Spencerian +Spengler/M +Spenglerian +Spense/MR +Spenser/M +Spenserian +Sperry/M +Sphinx/M +Spica/M +Spiegel/M +Spielberg/M +Spike/M +Spillane/M +Spinoza/M +Spiro/M +Spitz/M +Spock/M +Spokane/M +Sposato/M +Springfield/M +Springsteen/M +Sprint/M +Sproul/M +Spuds/M +Sputnik +Squanto +Squaresville/M +Squibb/GM +Squibbing/M +Sr +Srinagar/M +St/M +Sta/M +Stace/M +Stacee/M +Stacey/M +Staci/M +Stacia/M +Stacie/M +Stacy/M +Stael/M +Stafani/M +Staffard/M +Stafford/M +Staffordshire/M +Staford/M +Stahl/M +Staley/M +Stalin/SM +Stalingrad/M +Stalinist +Stallone/M +Stamford/M +Stan/YMS +Standford/M +Standish/M +Stanfield/M +Stanford/M +Stanislas/M +Stanislaus/M +Stanislavsky/M +Stanislaw/M +Stanleigh/M +Stanley/M +Stanly/M +Stanton/M +Stanwood/M +Stapleton/M +Star/M +Stargate/M +Stark/M +Starkey/M +Starla/M +Starlene/M +Starlin/M +Starr/M +Statehouse's +Staten/M +Statler/M +Stauffer/M +Stavro/MS +Ste/M +Stearn/SM +Stearne/M +Steele/M +Steen/M +Stefa/M +Stefan/M +Stefania/M +Stefanie/M +Stefano/M +Steffane/M +Steffen/M +Steffi/M +Steffie/M +Stein/RM +Steinbeck/SM +Steinberg/M +Steinem/M +Steiner/M +Steinmetz/M +Steinway/M +Stella/M +Stendhal/M +Stendler/M +Stengel/M +Stepha/M +Stephan/M +Stephana/M +Stephani/M +Stephanie/M +Stephannie/M +Stephanus/M +Stephen/MS +Stephenie/M +Stephenson/M +Stephi/M +Stephie/M +Stephine/M +Sterling/M +Stern/M +Sternberg/M +Sterne/M +Sterno +Stesha/M +Stetson/SM +Steuben/M +Stevana/M +Steve/M +Steven/MS +Stevena/M +Stevenson/M +Stevie/M +Stevy/M +Steward/M +Stewart/M +Stieglitz/M +Stillman/M +Stillmann/M +Stillwell/M +Stilton/MS +Stimson/M +Stine/M +Stinky/M +Stirling/M +Stockhausen/M +Stockholm/M +Stockton/M +Stoddard/M +Stoic/MS +Stoicism/SM +Stokes/M +Stone/M +Stonehenge/M +Stoppard/M +Storm/M +Stormi/M +Stormie/M +Stormy/M +Stouffer/M +Stout/M +Stowe/M +Strabo/M +Stradivari/SM +Stradivarius/M +Strasbourg/M +Stratford/M +Strauss +Stravinsky/M +Streisand/M +Strickland/M +Strindberg/M +Strom/M +Stromberg/M +Stromboli/M +Strong/M +Strongheart/M +Stu/M +Stuart/MS +Stubblefield/MS +Studebaker/M +Sturm/M +Stuttgart/M +Stuyvesant/M +Stygian +Styrofoam/S +Styx/M +Suarez/M +Subaru/M +Sucre/M +Sudan/M +Sudanese/M +Sudanic/M +Sudetenland/M +Sue/M +Suellen/M +Suetonius/M +Suez/M +Suffolk/M +Sufi/M +Sufism/M +Suharto/M +Sui/M +Sukarno/M +Sukey/M +Suki/M +Sukkot/S +Sukkoth's +Sula/M +Sulawesi/M +Suleiman/M +Sulla/M +Sullivan/M +Sully/M +Sulzberger/M +Sumatra/M +Sumatran/S +Sumeria/M +Sumerian/M +Summer/SM +Summerdale/M +Sumner/M +Sumter/M +Sun +Sunbelt/M +Sundanese/M +Sundas +Sunday/MS +Sung/M +Sunni/MS +Sunnite/SM +Sunny/M +Sunnyvale/M +Sunshine/M +Superior/M +Superman/M +Supt/M +Surabaya/M +Surat/M +Surinam's +Suriname +Surinamese +Surya/M +Sus +Susan/M +Susana/M +Susanetta/M +Susann/M +Susanna/M +Susannah/M +Susanne/M +Susette/M +Susi/M +Susie/M +Susquehanna/M +Sussex/M +Susy/M +Sutherlan/M +Sutherland/M +Sutton/M +Suva/M +Suwanee/M +Suzann/M +Suzanna/M +Suzanne/M +Suzette/M +Suzhou/M +Suzi/M +Suzie/M +Suzuki/M +Suzy/M +Svalbard/M +Sven/M +Svend/M +Svengali +Sverdlovsk/M +Svetlana/M +Swabian/SM +Swahili/MS +Swanee/M +Swansea/M +Swanson/M +Swarthmore/M +Swartz/M +Swazi/SM +Swaziland/M +Swed/MN +Swede/SM +Sweden/M +Swedenborg/M +Swedish +Sweeney/SM +Sweet/M +Swen/M +Swenson/M +Swift/M +Swinburne/M +Swink/M +Swiss/S +Switz/MR +Switzer/M +Switzerland/M +Sybil/M +Sybila/M +Sybilla/M +Sybille/M +Sybyl/M +Syd/M +Sydel/M +Sydelle/M +Sydney/M +Sykes/M +Sylas/M +Sylow/M +Sylvan/M +Sylvania/M +Sylvester/M +Sylvia/M +Sylvie/M +Syman/M +Symington/M +Symon/M +Synge/M +Syracuse/M +Syria/M +Syriac/M +Syrian/SM +Szilard/M +Szymborska/M +T'ang +T's +T/G +TA +TB +TBA +TCP +TD +TDD +TEFL +TELNET/M +TENEX/M +TESL +TESOL +TEirtza/M +THC +TKO +TLC +TM +TN +TNT +TOEFL +TRW +TTL +TV/M +TVA +TVs +TWA/M +TWX +TX +Ta/M +Tab/MR +Tabasco/MS +Tabatha/M +Tabb/M +Tabbatha/M +Tabbi/M +Tabbie/M +Tabbitha/M +Tabby/M +Taber/M +Tabernacle/S +Tabina/M +Tabitha/M +Tabor/M +Tabriz/SM +Tacitus/M +Tacoma/M +Tad/M +Tadd/M +Taddeo/M +Taddeusz/M +Tadeas/M +Tadeo/M +Tades +Tadio/M +Tadzhikistan's +Tadzhikstan/M +Taegu/M +Taejon/M +Taffy/M +Taft/M +Tagalog/SM +Tagore/M +Tagus/M +Tahiti/M +Tahitian/S +Tahoe/M +Taichung/M +Tailor/M +Tainan/M +Taine/M +Taipei/M +Tait/M +Taite/M +Taiwan/M +Taiwanese +Taiyuan/M +Tajikistan +Taklamakan/M +Talbert/M +Talbot/M +Talia/M +Taliesin/M +Talladega/M +Tallahassee/M +Tallahatchie/M +Tallahoosa/M +Tallchief/M +Talley/M +Talleyrand/M +Tallia/M +Tallie/M +Tallinn/M +Tallou/M +Tallulah/M +Tally/M +Talmud/MS +Talmudic +Talmudist/MS +Talya/M +Talyah/M +Tam/M +Tamar/M +Tamara/M +Tamarah/M +Tamarra/M +Tamas +Tameka/M +Tamera/M +Tamerlane/M +Tami/M +Tamika/M +Tamiko/M +Tamil/MS +Tamma/M +Tammany/M +Tammara/M +Tammi/M +Tammie/M +Tammy/M +Tampa/M +Tampax/M +Tamqrah/M +Tamra/M +Tan/M +Tana/M +Tanaka/M +Tananarive/M +Tancred/M +Tandi/M +Tandie/M +Tandy/M +Taney/M +Tanganyika/M +Tangier/M +Tangshan/M +Tanhya/M +Tani/M +Tania/M +Tanisha/M +Tanitansy/M +Tann/RM +Tannenbaum/M +Tanner/M +Tanney/M +Tannhäuser/M +Tannie/M +Tanny/M +Tansy/M +Tantalus/M +Tanya/M +Tanzania/M +Tanzanian/S +Tao/M +Taoism/MS +Taoist/MS +Tapdance/M +Tara/M +Tarah/M +Tarawa/M +Tarazed/M +Tarbell/M +Tarim/M +Tarkington/M +Tarra/M +Tarrah/M +Tarrance/M +Tarrytown/M +Tartar's +Tartary/M +Tartuffe/M +Taryn/M +Tarzan/M +Tasha/M +Tashkent/M +Tasia/M +Tasmania/M +Tasmanian/S +Tass/M +Tatar/SM +Tate/M +Tatiana/M +Tatiania/M +Tatum/M +Taurus/SM +Tawney/M +Tawnya/M +Tawsha/M +Taylor/SM +Tb +Tbilisi/M +Tc/M +Tchaikovsky/M +Te +TeX/M +Teador/M +Teasdale/M +Technicolor/MS +Technion/M +Tecumseh/M +Ted/M +Tedd/M +Tedda/M +Teddi/M +Teddie/M +Teddy/M +Tedi/M +Tedie/M +Tedman/M +Tedmund/M +Tedra/M +Teena/M +Teflon/MS +Tegucigalpa/M +Teheran's +Tehran +Tektronix/M +TelePrompTer/S +TelePrompter/M +Teledyne/M +Telefunken/M +Telemachus/M +Telemann/M +Teletype/SM +Telex/M +Tell/MR +Teller/M +Telnet/M +Telugu/M +Temp/M +Tempe/M +Temple/M +Templeman/M +Templeton/M +Tenex/M +Tenn/M +Tenneco/M +Tennessean/S +Tennessee/M +Tenney/M +Tennyson/M +Tenochtitlan/M +Teodoor/M +Teodor/M +Teodora/M +Teodorico/M +Teodoro/M +Tera/M +Terence/M +Terencio/M +Teresa/M +Terese/M +Tereshkova/M +Teresina/M +Teresita/M +Teressa/M +Teri/M +Teriann/M +Terkel/M +Terpsichore/M +Terra/M +Terran/M +Terrance/M +Terre/M +Terrel/M +Terrell/M +Terrence/M +Terri/M +Terrie/M +Terrijo/M +Terrill/M +Territorial/SM +Territory's +Terry/M +Terrye/M +Tersina/M +Tertiary +Terza/M +Tesla/M +Tess/M +Tessa/M +Tessi/M +Tessie/M +Tessy/M +Tethys/M +Tetons +Teuton/SM +Teutonic +Tex/M +Texaco/M +Texan/S +Texas/MS +Textron/M +Th/M +Thacher/M +Thackeray/M +Thad/M +Thaddeus/M +Thaddus/M +Thadeus/M +Thai/S +Thailand/M +Thain/M +Thaine/M +Thales/M +Thalia/M +Thames +Thane/M +Thanh/M +Thanksgiving/S +Thant/M +Thar/M +Thatch/MR +Thatcher/M +Thaxter/M +Thayer/M +Thayne/M +Thea/M +Theadora/M +Thebault/M +Thebes +Theda/M +Thedric/M +Thedrick/M +Theiler/M +Thekla/M +Thelma/M +Themistocles/M +Theo/M +Theobald/M +Theocritus/M +Theodor/M +Theodora/M +Theodore/M +Theodoric/M +Theodosia/M +Theodosian +Theodosius/M +Theosophy +Theravada/M +Theresa/M +Therese/M +Theresina/M +Theresita/M +Theressa/M +Therine/M +Thermos/SM +Theron/M +Theseus/M +Thespian/S +Thespis/M +Thessalonian +Thessaloníki/M +Thessaly/M +Thia/M +Thibaud/M +Thibaut/M +Thiensville/M +Thieu/M +Thimbu/M +Thimphu +Thom/M +Thoma/SM +Thomasa/M +Thomasin/M +Thomasina/M +Thomasine/M +Thomism/M +Thomistic +Thompson/M +Thomson/M +Thor/M +Thorazine +Thoreau/M +Thorin/M +Thorn/M +Thornburg/M +Thorndike/M +Thornie/M +Thornton/M +Thorny/M +Thorpe/M +Thorstein/M +Thorsten/M +Thorvald/M +Thoth/M +Thrace/M +Thracian/M +Throneberry/M +Thruway/MS +Thu +Thucydides/M +Thule/M +Thunderbird/M +Thur/MS +Thurber/M +Thurman/M +Thursday/SM +Thurstan/M +Thurston/M +Ti/M +Tia/M +Tianjin +Tiber/M +Tiberius/M +Tibet/M +Tibetan/S +Tibold/M +Tiburon/M +Ticonderoga/M +Tiebold/M +Tiebout/M +Tieck/M +Tiena/M +Tienanmen/M +Tientsin's +Tierney/M +Tiertza/M +Tiff/M +Tiffani/M +Tiffanie/M +Tiffany/M +Tiffi/M +Tiffie/M +Tiffy/M +Tigris/M +Tijuana/M +Tilda/M +Tildi/M +Tildie/M +Tildy/M +Tiler/M +Tillich/M +Tillie/M +Tillman/M +Tilly/M +Tim/MS +Timbuktu/M +Timex/M +Timi/M +Timmi/M +Timmie/M +Timmy/M +Timofei/M +Timon/M +Timoteo/M +Timothea/M +Timothee/M +Timotheus/M +Timothy/M +Timur/M +Tina/M +Tine/M +Ting/M +Tinkertoy +Tinseltown/M +Tintoretto/M +Tioga/M +Tiphani/M +Tiphanie/M +Tiphany/M +Tippecanoe/M +Tipperary/M +Tirana's +Tirane +Tiresias/M +Tirol/M +Tirolean/S +Tirrell/M +Tish/M +Tisha/M +Titan/SM +Titania/M +Titanic/M +Titian/M +Titicaca/M +Tito/SM +Titus/M +Tl/M +Tlaloc/M +Tlingit/M +Tm/M +Tobago/M +Tobe/M +Tobey/M +Tobi/M +Tobiah/M +Tobias/M +Tobie/M +Tobin/M +Tobit/M +Toby/M +Tobye/M +Tocantins/M +Tocqueville +Tod/M +Todd/M +Toddie/M +Toddy/M +Togo/M +Togolese/M +Toiboid/M +Toinette/M +Tojo/M +Tokay/M +Tokugawa/M +Tokyo/M +Tokyoite/MS +Toland/M +Toledo/SM +Tolkien +Tolley/M +Tolstoy/M +Tolyatti/M +Tom/M +Toma/SM +Tomasina/M +Tomasine/M +Tomaso/M +Tombaugh/M +Tombigbee/M +Tome/M +Tomi/M +Tomkin/M +Tomlin/M +Tommi/M +Tommie/M +Tommy/M +Tompkins/M +Tomsk/M +Tonga/M +Tongan/SM +Toni/M +Tonia/M +Tonie/M +Tonio/M +Tonnie/M +Tonto/M +Tony/M +Tonya/M +Tonye/M +Toomey/M +Tootsie/M +Topeka/M +Topsy/M +Torah/M +Torahs +Tore/M +Torey/M +Tori/M +Torie/M +Torin/M +Toronto/M +Torquemada/M +Torr/XM +Torrance/M +Torre/MS +Torrence/M +Torrens/M +Torrey/M +Torricelli/M +Torrie/M +Torrin/M +Torry/M +Tortola/M +Tortuga/M +Tory/SM +Tosca/M +Toscanini/M +Toshiba/M +Toto/M +Toulouse/M +Tova/M +Tove/M +Town/M +Townes +Towney/M +Townie/M +Townley/M +Townsend/M +Towny/M +Towsley/M +Toynbee/M +Toyoda/M +Toyota/M +Trace/M +Tracee/M +Tracey/M +Traci/M +Tracie/M +Tractarians +Tracy/M +Trafalgar/M +Trajan/M +Tran/M +Transcaucasia/M +Transite/M +Transputer/M +Transvaal/M +Transylvania/M +Trappist/MS +Trastevere/M +Traver/MS +Travis/M +Travus/M +Treadwell/M +Treasury/SM +Treblinka/M +Trefor/M +Trekkie/M +Tremain/M +Tremaine/M +Tremayne/M +Trenna/M +Trent/M +Trenton/M +Tresa/M +Trescha/M +Tressa/M +Trev/RM +Trevar/M +Trevelyan/M +Trever/M +Trevino/M +Trevor/M +Trey/M +Triangulum/M +Trianon/M +Triassic +Tricia/M +Trieste/M +Trimble/M +Trimurti/M +Trina/M +Trinidad/M +Trinity/MS +Trip/M +Tripoli/M +Tripp/M +Trippe/M +Tris +Trish/M +Trisha/M +Trista/M +Tristam/M +Tristan/M +Triton/M +Trix/M +Trixi/M +Trixie/M +Trixy/M +Trobriand/M +Trojan/MS +Trollope/M +Trondheim/M +Tropez/M +Trotsky/M +Troutman/M +Troy/M +Troyes +Trstram/M +Truckee/M +Truda/M +Trude/M +Trudeau/M +Trudey/M +Trudi/M +Trudie/M +Trudy/M +Trueman/M +Trujillo/M +Trula/M +Trumaine/M +Truman/M +Trumann/M +Trumbull/M +Trump/M +Truth +Tsimshian/M +Tsiolkovsky/M +Tsitsihar/M +Tsunematsu/M +Tswana/M +Tuamotu/M +Tuareg/M +Tubman/M +Tuck/RM +Tucker/M +Tuckie/M +Tucky/M +Tucson/M +Tucuman/M +Tudor/MS +Tue/S +Tuesday/SM +Tulane/M +Tull/M +Tulley/M +Tully/M +Tulsa/M +Tums/M +Tungus/M +Tunguska/M +Tunis/M +Tunisia/M +Tunisian/S +Tupi/M +Tupperware +Tupungato/M +Turgenev/M +Turin/M +Turing/M +Turk/SM +Turkestan/M +Turkey/M +Turkic/SM +Turkish +Turkmenistan/M +Turner/M +Turpin/M +Tuscaloosa/M +Tuscan +Tuscany/M +Tuscarora/M +Tuscon/M +Tuskegee/M +Tussuad/M +Tut/M +Tutankhamen/M +Tutsi +Tuttle/M +Tuvalu +Twain/M +Tweed/M +Tweedledee/M +Tweedledum/M +Twila/M +Twinkie +Twp +Twyla/M +Ty/M +Tybalt/M +Tybi/M +Tybie/M +Tye/M +Tylenol/M +Tyler/M +Tymon/M +Tymothy/M +Tynan/M +Tyndale/M +Tyndall/M +Tyne/M +Typhon/M +Tyree/M +Tyrol's +Tyrolean/S +Tyrone/M +Tyrus/M +Tyson/M +Tzeltal/M +U +UAR +UART +UAW +UCLA/M +UFO/S +UHF +UK +UL +ULTRIX/M +UN +UNESCO +UNICEF +UNIX/M +UPC +UPI +UPS +URL +US +USA +USAF +USART +USC/M +USCG +USDA +USG/M +USIA +USMC +USN +USO +USP +USPS +USS +USSR +UT +UV +Ubangi/M +Ubuntu +Ubuntu's +UbuntuOne +UbuntuOne's +Ucayali/M +Uccello/M +Udale/M +Udall/M +Udell/M +Ufa/M +Uganda/M +Ugandan/S +Ugo/M +Uighur +Ujungpandang/M +Ukraine/M +Ukrainian/S +Ula/M +Ulberto/M +Ulick/M +Ulises/M +Ulla/M +Ullman/M +Ulric/M +Ulrica/M +Ulrich/M +Ulrick/M +Ulrika/M +Ulrikaumeko/M +Ulrike/M +Ulster/M +Ultrasuede +Ultrix/M +Ulyanovsk/M +Ulysses/M +Umberto/M +Umbriel/M +Umeko/M +Una/M +Underwood/M +Ungava/M +UniPlus/M +UniSoft/M +Unibus/M +Unicode/M +Union/MS +Unionist/SM +Uniroyal/M +Unisys/M +Unitarian/MS +Unitarianism/SM +Univac/M +Unix/M +Unukalhai/M +Upanishads +Updike/M +Upton/M +Ur/M +Ural/MS +Urania/M +Uranus/M +Urbain/M +Urban/M +Urbana/M +Urbano/M +Urbanus/M +Urdu/M +Urey/M +Uri/SM +Uriah/M +Uriel/M +Urquhart/M +Ursa/M +Ursala/M +Ursola/M +Urson/M +Ursula/M +Ursulina/M +Ursuline/M +Uruguay/M +Uruguayan/S +Urumqi +Usenet/M +Usenix/M +Ustinov/M +Uta/M +Utah/M +Utahan/SM +Ute/M +Utica/M +Utopia/MS +Utopian/S +Utrecht/M +Utrillo/M +Uzbek/M +Uzbekistan +Uzi/M +V +V's +VA +VAR +VAT +VAX/M +VAXes +VCR +VD +VDT +VDU +VF +VFW +VG +VGA +VHF +VHS +VI +VIP/S +VISTA +VJ +VLF +VLSI +VMS/M +VOA +VP +VT +VTOL +Va/M +Vachel/M +Vaclav/M +Vader/M +Vaduz/M +Vail/M +Val/MY +Valaree/M +Valaria/M +Valarie/M +Valdemar/M +Valdez/M +Vale/M +Valeda/M +Valencia/MS +Valene/M +Valenka/M +Valentia/M +Valentijn/M +Valentin/M +Valentina/M +Valentine/M +Valentino/M +Valenzuela/M +Valera/M +Valeria/M +Valerian/M +Valerie/M +Valerye/M +Valhalla/M +Valida/M +Valina/M +Valium/S +Valkyrie/SM +Valle/M +Vallejo +Valletta/M +Valli/M +Vallie/M +Vally/M +Valma/M +Valois/M +Valparaiso/M +Valry/M +Valéry/M +Van/M +Vance/M +Vancouver/M +Vanda/M +Vandal/MS +Vandenberg/M +Vanderbilt/M +Vanderburgh/M +Vanderpoel/M +Vandyke/SM +Vanessa/M +Vang/M +Vania/M +Vanna/M +Vanni/M +Vannie/M +Vanny/M +Vanuatu +Vanya/M +Vanzetti/M +Varanasi/M +Varese/M +Vargas/M +Varian/M +Varityping/M +Vaseline/DSMG +Vasili/MS +Vasily/M +Vasquez/M +Vassar/M +Vassili/M +Vassily/M +Vatican/M +Vaudois +Vaughan/M +Vaughn/M +Vax/M +Vazquez/M +Veblen/M +Veda/MS +Vedanta/M +Vega/SM +Vegemite/M +Vela/M +Velcro/SM +Velez/M +Vella/M +Velma/M +Velveeta/M +Velvet/M +Velásquez/M +Velázquez +Venetian/SM +Venezuela/M +Venezuelan/S +Venice/M +Venita/M +Venn/M +Ventura/M +Venus/S +Venusian/S +Vera/M +Veracruz/M +Veradis +Verde/M +Verderer/M +Verdi/M +Vere/M +Verena/M +Verene/M +Verge/M +Vergil's +Veriee/M +Verile/M +Verina/M +Verine/M +Verla/M +Verlag/M +Verlaine/M +Vermeer/M +Vermont/ZRM +Vermonter/M +Vern/NM +Verna/M +Verne/M +Vernen/M +Verney/M +Vernice/M +Vernon/M +Vernor/M +Verona/M +Veronese/M +Veronica/M +Veronika/M +Veronike/M +Veronique/M +Versailles/M +Versatec/M +Vesalius/M +Vespasian/M +Vespucci/M +Vesta/M +Vesuvius/M +Vevay/M +Vi/M +Viagra/M +Vic/M +Vicente/M +Vichy/M +Vick/ZM +Vickers/M +Vicki/M +Vickie/M +Vicksburg/M +Vicky/M +Victoir/M +Victor/M +Victoria/M +Victorian/S +Victorianism/S +Victrola/SM +Vida/M +Vidal/M +Vidovic/M +Vidovik/M +Vienna/M +Viennese/M +Vientiane/M +Viet/M +Vietcong/M +Vietminh/M +Vietnam/M +Vietnamese/M +Vijayawada/M +Viki/M +Viking/MS +Vikki/M +Vikky/M +Vikram/M +Vila +Vilhelmina/M +Villa/M +Villarreal/M +Villon/M +Vilma/M +Vilnius/M +Vilyui/M +Vin/M +Vina/M +Vince/M +Vincent/MS +Vincenty/M +Vincenz/M +Vinci/M +Vindemiatrix/M +Vinita/M +Vinni/M +Vinnie/M +Vinny/M +Vinson/M +Viola/M +Violante/M +Viole/M +Violet/M +Violetta/M +Violette/M +Virge/M +Virgie/M +Virgil/M +Virgilio/M +Virgina/M +Virginia/M +Virginian/S +Virginie/M +Virgo/MS +Visa/M +Visakhapatnam's +Visayans +Vishnu/M +Visigoth/M +Visigoths +Vistula/M +Vita/M +Vite/M +Vitia/M +Vitim/M +Vito/M +Vitoria/M +Vittoria/M +Vittorio/M +Vitus/M +Viv/M +Viva/M +Vivaldi +Vivekananda/M +Vivi/MN +Vivia/M +Vivian/M +Viviana/M +Vivianna/M +Vivianne/M +Vivie/M +Vivien/M +Viviene/M +Vivienne/M +Viviyan/M +Vivyan/M +Vivyanne/M +Vlad/M +Vladamir/M +Vladimir/M +Vladivostok/M +Vogel/M +Vol/M +Volga/M +Volgograd/M +Volkswagen/SM +Volstead/M +Volta/M +Voltaire/M +Volterra/M +Volvo/M +Von/M +Vonda/M +Vonnegut/M +Vonni/M +Vonnie/M +Vonny/M +Voronezh/M +Vorster/M +Vt/M +Vulcan/M +Vulg/M +Vulgate/SM +Vyky/M +W's +W/T +WA +WAC +WASP +WATS +WC +WFF +WHO +WI +WNW +WP +WSW +WV +WW +WWI +WWII +WWW +WY +WYSIWYG +Waals +Wabash/M +Wac/S +Wacke/M +Waco/M +Wade/M +Wadsworth/M +Wafs +Wagner/M +Wagnerian +Wahl/M +Waikiki/M +Wain/M +Wainwright/M +Wait/MR +Waite/M +Waiter/M +Wake/M +Wakefield/M +Waksman/M +Walbridge/M +Walcott/M +Wald/MN +Waldemar/M +Walden/M +Waldensian +Waldheim/M +Waldo/M +Waldon/M +Waldorf/M +Wales +Walesa/M +Walford/M +Walgreen/M +Walker/M +Walkman/S +Wall/SMR +Wallace/M +Wallache/M +Wallas/M +Wallenstein/M +Waller/M +Wallie/M +Wallis +Walliw/M +Walloon/SM +Wally/M +Walpole/M +Walpurgisnacht +Walsh/M +Walt/ZMR +Walter/M +Walther/M +Walton/M +Walworth/M +Waly/M +Wanamaker/M +Wanda/M +Wandie/M +Wandis/M +Waneta/M +Wang/M +Wanids/M +Wankel/M +Wansee/M +Wansley/M +Ward/MN +Warde/M +Warden/M +Ware/MG +Warfield/M +Warhol/M +Waring/M +Warner/M +Warnock/M +Warren/M +Warsaw/M +Warwick/M +Wasatch/M +Wash/M +Washburn/M +Washington/M +Washingtonian/S +Washoe/M +Wasp's +Wasserman/M +Wassermann/M +Wat/ZM +Watanabe/M +Waterbury/M +Watergate/M +Waterhouse/M +Waterloo/SM +Waters/M +Watertown/M +Watkins +Watson/M +Watt/MS +Watteau/M +Wattenberg/M +Watterson/M +Watusi/M +Waugh/M +Waukesha/M +Waunona/M +Waupaca/M +Waupun/M +Wausau/M +Wauwatosa/M +Wave/S +Waveland/M +Waverley/M +Waverly/M +Way/M +Waylan/M +Wayland/M +Waylen/M +Waylin/M +Waylon/M +Wayne/M +Waynesboro/M +Weatherford/M +Weaver/M +Web/MR +Webb/RM +Webber/M +Weber/M +Webern/M +Webster/MS +Websterville/M +Wed/M +Weddell/M +Wedgwood/M +Wednesday/SM +Weeks/M +Wehr/M +Wei/M +Weibull/M +Weidar/M +Weider/M +Weidman/M +Weierstrass/M +Weill/M +Weinberg/M +Weiner/M +Weinstein/M +Weisenheimer/M +Weiss/M +Weissman/M +Weissmuller/M +Weizmann/M +Welbie/M +Welby/M +Welcher/M +Welches +Weldon/M +Weldwood/M +Welland/M +Weller/M +Welles/M +Wellesley/M +Wellington/MS +Wellman/M +Wells/M +Wellsville/M +Welmers/M +Welsh +Welshman/M +Welshmen +Welshwoman/M +Welshwomen +Wenda/M +Wendall/M +Wendel/M +Wendeline/M +Wendell/M +Wendi/M +Wendie/M +Wendy/M +Wendye/M +Wenona/M +Wenonah/M +Wentworth/M +Werner/M +Wernher/M +Werther/M +Wes +Wesley/M +Wesleyan +Wessex/M +Wesson/M +West/MS +Westbrook/M +Westbrooke/M +Westchester/M +Western/ZRS +Westfield/M +Westhampton/M +Westinghouse/M +Westleigh/M +Westley/M +Westminster/M +Westmore/M +Weston/M +Westphalia/M +Westport/M +Westwood/M +Weyden/M +Weyerhauser/M +Weylin/M +Wezen/M +Whalen/M +Wharton/M +Wheaties/M +Wheatland/M +Wheaton/M +Wheatstone/M +Wheeler/M +Wheeling/M +Wheelock/M +Whelan/M +Wheller/M +Whig/SM +Whippany/M +Whipple/M +Whistler/M +Whit/M +Whitaker/M +Whitby/M +Whitcomb/M +White/MS +Whitefield/M +Whitehall/M +Whitehead/M +Whitehorse/M +Whiteleaf/M +Whiteley/M +Whitewater/M +Whitfield/M +Whitley/M +Whitlock/M +Whitman/M +Whitney/M +Whitsunday/MS +Whittaker/M +Whittier +Wiatt/M +Wichita/M +Wieland/M +Wiemar/M +Wier/M +Wiesel/M +Wiggins +Wigner/M +Wilberforce/M +Wilbert/M +Wilbur/M +Wilburn/M +Wilburt/M +Wilcox/M +Wilda/M +Wilde/MR +Wilden/M +Wilder/M +Wildon/M +Wileen/M +Wilek/M +Wiley/M +Wilford/M +Wilfred/M +Wilfredo/M +Wilfrid/M +Wilhelm/M +Wilhelmina/M +Wilhelmine/M +Wilie/M +Wilkerson/M +Wilkes/M +Wilkins/M +Wilkinson/M +Will/M +Willa/M +Willabella/M +Willamette/M +Willamina/M +Willard/M +Willcox/M +Willdon/M +Willem/M +Willemstad/M +Willetta/M +Willette/M +Willey/M +Willi/MS +William/SM +Williamsburg/M +Williamson/M +Willie/M +Willied/M +Willisson/M +Willoughby/M +Willow/M +Willy/SDM +Willyt/M +Wilma/M +Wilmar/M +Wilmer/M +Wilmette/M +Wilmington/M +Wilona/M +Wilone/M +Wilow/M +Wilshire/M +Wilson/M +Wilsonian +Wilt/M +Wilton/M +Wimbledon/M +Win/M +Winchell/M +Winchester/MS +Windham/M +Windhoek/M +Windows +Windsor/MS +Windward/M +Windy/M +Winehead/M +Winesap/M +Winfield/M +Winfred/M +Winfrey/M +Wini/M +Winifield/M +Winifred/M +Winkle/M +Winn/M +Winna/M +Winnah/M +Winne/M +Winnebago/M +Winnetka/M +Winni/M +Winnie/M +Winnifred/M +Winnipeg/M +Winny/M +Winograd/M +Winona/M +Winonah/M +Winooski/M +Winsborough/M +Winsett/M +Winslow/M +Winston/M +Winters +Winthrop/M +Wis/M +Wisc +Wisconsin/M +Wisconsinite/SM +Wise/M +Wisenheimer/M +Wit/M +Witherspoon/M +Witt/M +Wittgenstein/M +Wittie/M +Witty/M +Witwatersrand/M +Wm/M +Wodehouse/M +Wolcott/M +Wolf/M +Wolfe/M +Wolff/M +Wolfgang/M +Wolfie/M +Wolfy/M +Wollongong/M +Wollstonecraft/M +Wolsey/M +Wolverhampton/M +Wolverton/M +Wong/M +Wood/SM +Woodard/M +Woodberry/M +Woodbury/M +Woodhull/M +Woodie/M +Woodlawn/M +Woodman/M +Woodrow/M +Woodstock/M +Woodward/MS +Woody/M +Woolf/M +Woolongong/M +Woolworth/M +Woonsocket/M +Wooster/M +Wooten/M +Worcester/SM +Worcestershire/M +Worden/M +Wordsworth/M +Workman/M +Worms/M +Worth/M +Worthington/M +Worthy/M +Wotan/M +Wozniak/M +Wrangell/M +Wren/MS +Wrennie/M +Wright/M +Wrigley/M +Wroclaw +Wronskian/M +Wu/M +Wuhan/M +Wurlitzer/M +Wyatan/M +Wyatt/M +Wycherley/M +Wycliffe/M +Wye/MH +Wyeth/M +Wylie/M +Wylma/M +Wyman/M +Wyn/M +Wyndham/M +Wynn/M +Wynne/M +Wynnie/M +Wynny/M +Wyo/M +Wyoming/M +Wyomingite/SM +X +X's +XEmacs/M +XL +XML +XOR +XS +XXL +Xanadu +Xanthippe/M +Xanthus/M +Xavier/M +Xaviera/M +Xe/M +Xebec/M +Xena/M +Xenakis/M +Xenia/M +Xenix/M +Xenophon/M +Xenos +Xerox/MGSD +Xerxes/M +Xever/M +Xhosa/M +Xi'an +Xian/S +Xiaoping/M +Ximenes/M +Ximenez/M +Ximian/SM +Xingu/M +Xmas/SM +Xochipilli/M +Xuzhou/M +Xylia/M +Xylina/M +Xymenes/M +Y +Y's +YMCA +YMHA +YMMV +YT +YWCA +YWHA +Yacc/M +Yagi/M +Yahweh/M +Yakima/M +Yakut/M +Yakutsk/M +Yale/M +Yalies/M +Yalonda/M +Yalow/M +Yalta/M +Yalu/M +Yamaha/M +Yamoussoukro +Yanaton/M +Yance/M +Yancey/M +Yancy/M +Yang/M +Yangon +Yangtze/M +Yank/MS +Yankee/SM +Yaounde/M +Yaqui/M +Yard/M +Yardley/M +Yaroslavl/M +Yasmeen/M +Yasmin/M +Yates +Yb/M +Yeager/M +Yeats/M +Yehudi/M +Yehudit/M +Yekaterinburg/M +Yelena/M +Yellowknife/M +Yellowstone/M +Yeltsin +Yemen/M +Yemeni/S +Yemenite/SM +Yenisei/M +Yentl/M +Yerevan/M +Yerkes/M +Yesenia/M +Yetta/M +Yettie/M +Yetty/M +Yevette/M +Yevtushenko/M +Yggdrasil/M +Yiddish/M +Ymir/M +Ynes/M +Ynez/M +Yoda/M +Yoder/M +Yoknapatawpha/M +Yoko/M +Yokohama/M +Yolanda/M +Yolande/M +Yolane/M +Yolanthe/M +Yong/M +Yonkers/M +Yorgo/MS +Yorick/M +York/ZRMS +Yorke/M +Yorker/M +Yorkshire/MS +Yorktown/M +Yoruba/M +Yosemite/M +Yoshi/M +Yoshiko/M +Yost/M +Young/M +Youngstown/M +Yovonnda/M +Ypres/M +Ypsilanti/M +Ysabel/M +Yuba/M +Yucatan +Yugo/M +Yugoslav/M +Yugoslavia/M +Yugoslavian/S +Yuh/M +Yuki/M +Yukon/M +Yul/M +Yule/MS +Yuletide/S +Yulma/M +Yuma/M +Yunnan/M +Yuri/M +Yurik/M +Yves/M +Yvette/M +Yvon/M +Yvonne/M +Yvor/M +Z/X +Zabrina/M +Zaccaria/M +Zach/M +Zacharia/SM +Zachariah/M +Zacharie/M +Zachary/M +Zacherie/M +Zachery/M +Zack/M +Zackariah/M +Zagreb/M +Zahara/M +Zaire/M +Zairian/S +Zak/M +Zambezi/M +Zambia/M +Zambian/S +Zamboni +Zamenhof/M +Zamora/M +Zan/M +Zandra/M +Zane/M +Zaneta/M +Zanuck/M +Zanzibar/M +Zapata/M +Zaporozhye/M +Zappa/M +Zara/M +Zarah/M +Zared/M +Zaria/M +Zarla/M +Zea/M +Zealand/M +Zeb/M +Zebadiah/M +Zebedee/M +Zebulen/M +Zebulon/M +Zechariah/M +Zed/M +Zedekiah/M +Zedong/M +Zeffirelli/M +Zeiss/M +Zeke/M +Zelda/M +Zelig/M +Zellerbach/M +Zelma/M +Zen/M +Zena/M +Zenger/M +Zenia/M +Zennist/M +Zeno/M +Zephaniah/M +Zephyrus/M +Zeppelin's +Zerk/M +Zeus/M +Zhdanov/M +Zhengzhou +Zhivago/M +Zhukov/M +Zia/M +Zibo/M +Ziegfeld/MS +Ziegler/M +Ziggy/M +Zilvia/M +Zimbabwe/M +Zimbabwean/S +Zimmerman/M +Zion/SM +Zionism/MS +Zionist/MS +Zita/M +Zitella/M +Zn/M +Zoe/M +Zola/M +Zollie/M +Zolly/M +Zomba/M +Zonda/M +Zondra/M +Zonnya/M +Zora/M +Zorah/M +Zorana/M +Zorina/M +Zorine/M +Zorn/M +Zoroaster/M +Zoroastrian/S +Zoroastrianism/MS +Zorro/M +Zosma/M +Zr/M +Zs +Zsazsa/M +Zsigmondy/M +Zubenelgenubi/M +Zubeneschamali/M +Zukor/M +Zulema/M +Zulu/MS +Zululand/M +Zuni/S +Zuzana/M +Zwingli/M +Zworykin/M +Zürich/M +a +aardvark/SM +ab/DY +aback +abacus/SM +abaft +abalone/SM +abandon/LGDRS +abandoner/M +abandonment/SM +abase/LGDSR +abasement/S +abaser/M +abash/SDLG +abashed/UY +abashment/MS +abate/DSRLG +abated/U +abatement/MS +abater/M +abattoir/SM +abbess/SM +abbey/MS +abbot/MS +abbr +abbrev +abbreviate/XDSNG +abbreviated/UA +abbreviates/A +abbreviating/A +abbreviation/M +abbé/S +abdicate/NGDSX +abdication/M +abdomen/SM +abdominal/YS +abduct/DGS +abduction/SM +abductor/SM +abeam +aberrant/YS +aberration/SM +aberrational +abet/S +abetted +abetting +abettor/SM +abeyance/MS +abeyant +abhor/S +abhorred +abhorrence/MS +abhorrent/Y +abhorrer/M +abhorring +abidance/MS +abide/JGSR +abider/M +abiding/Y +ability/IMES +abject/SGPDY +abjection/MS +abjectness/SM +abjuration/SM +abjuratory +abjure/ZGSRD +abjurer/M +ablate/VGNSDX +ablation/M +ablative/SY +ablaze +able/U +abler/E +ables/E +ablest +abloom +ablution/MS +abnegate/NGSDX +abnegation/M +abnormal/SY +abnormality/SM +aboard +abode/GMDS +abolish/LZRSDG +abolisher/M +abolishment/MS +abolition/SM +abolitionism/SM +abolitionist/SM +abominable +abominably +abominate/XSDGN +abomination/M +aboriginal/YS +aborigine/SM +aborning +abort/SRDVG +abortion/MS +abortionist/MS +abortive/PY +abortiveness/M +abound/GDS +about/S +above/S +aboveboard +aboveground +abracadabra/S +abrade/SRDG +abrader/M +abrasion/MS +abrasive/SYMP +abrasiveness/S +abreaction/MS +abreast +abridge/DSRG +abridged/U +abridger/M +abridgment/SM +abroad +abrogate/XDSNG +abrogation/M +abrogator/SM +abrupt/TRYP +abruptness/SM +abs/M +abscess/GDSM +abscissa/SM +abscission/SM +abscond/SDRZG +absconder/M +abseil/SGDR +absence/SM +absent/SGDRY +absentee/MS +absenteeism/SM +absentia/M +absentminded/PY +absentmindedness/S +absinthe/SM +absolute/NPRSYTX +absoluteness/SM +absolution/M +absolutism/MS +absolutist/SM +absolve/GDSR +absolver/M +absorb/ASGD +absorbed/U +absorbency/MS +absorbent/MS +absorber/SM +absorbing/Y +absorption/MS +absorptive +absorptivity/M +abstain/GSDRZ +abstainer/M +abstemious/YP +abstemiousness/MS +abstention/SM +abstinence/MS +abstinent/Y +abstract/PTVGRDYS +abstracted/YP +abstractedness/SM +abstracter/M +abstraction/SM +abstractionism/M +abstractionist/SM +abstractness/SM +abstractor/MS +abstruse/PRYT +abstruseness/SM +absurd/PRYST +absurdity/SM +absurdness/SM +abundance/SM +abundant/Y +abuse/GVZDSRB +abused/E +abuser/M +abuses/E +abusing/E +abusive/YP +abusiveness/SM +abut/LS +abutment/SM +abutted +abutter/MS +abutting +abuzz +abysmal/Y +abyss/SM +abyssal +ac/DRG +acacia/SM +academe/MS +academia/SM +academic/S +academical/Y +academician/SM +academicianship +academy/SM +acanthus/MS +accede/SDG +accelerate/NGSDXV +accelerated/U +accelerating/Y +acceleration/M +accelerator/SM +accelerometer/SM +accent/SGMD +accented/U +accentual/Y +accentuate/XNGSD +accentuation/M +accept/RDBSZVG +acceptability's/U +acceptability/SM +acceptable/P +acceptableness/SM +acceptably/U +acceptance/SM +acceptant +acceptation/SM +accepted/Y +accepter/M +accepting/PY +acceptor/MS +access/SDMG +accessed/A +accessibility/IMS +accessible/IU +accessibly/I +accession/SMDG +accessors +accessory/SM +accidence/M +accident/MS +accidental/SPY +accidentalness/M +acclaim/SDRG +acclaimer/M +acclamation/MS +acclimate/XSDGN +acclimation/M +acclimatisation +acclimatise/DG +acclimatization/AMS +acclimatize/RSDGZ +acclimatized/U +acclimatizes/A +acclivity/SM +accolade/GDSM +accommodate/XVNGSD +accommodated/U +accommodating/Y +accommodation/M +accommodative/P +accommodativeness/M +accompanied/U +accompanier/M +accompaniment/MS +accompanist/SM +accompany/DRSG +accomplice/MS +accomplish/SRDLZG +accomplished/U +accomplisher/M +accomplishment/SM +accord/SZGMRD +accordance/SM +accordant/Y +accorder/M +according/Y +accordion/MS +accordionist/SM +accost/SGD +account/BMDSGJ +accountability's/U +accountability/MS +accountable/U +accountableness/M +accountably/U +accountancy/SM +accountant/MS +accounted/U +accounting/M +accouter/GSD +accouterment's +accouterments +accoutrement/M +accredit/SGD +accreditation/SM +accredited/U +accretion/SM +accrual/MS +accrue/SDG +acct +acculturate/XSDVNG +acculturation/M +accumulate/VNGSDX +accumulation/M +accumulative/YP +accumulativeness/M +accumulator/MS +accuracy/IMS +accurate/IY +accurateness/SM +accursed/YP +accursedness/SM +accusal/M +accusation/SM +accusative/S +accusatory +accuse/SRDZG +accused/M +accuser/M +accusing/Y +accustom/SGD +accustomed/P +accustomedness/M +ace/SM +aced/M +acerbate/DSG +acerbic +acerbically +acerbity/MS +acetaminophen/S +acetate/MS +acetic +acetone/SM +acetonic +acetylene/MS +ache/DSG +ached/A +achene/SM +aches/A +achievable/U +achieve/LZGRSDB +achieved/UA +achievement/SM +achiever/M +aching/Y +achoo +achromatic +achy/TR +acid/SMYP +acidic +acidification/M +acidify/NSDG +acidity/SM +acidness/M +acidoses +acidosis/M +acidulous +acing/M +acknowledge/GZDRS +acknowledgeable +acknowledged/U +acknowledgedly +acknowledgement/SAM +acknowledger/M +acknowledgment/SAM +acme/SM +acne/MDS +acolyte/MS +aconite/MS +acorn/SM +acoustic/S +acoustical/Y +acoustician/M +acoustics/M +acquaint/GASD +acquaintance/MS +acquaintanceship/S +acquainted/U +acquiesce/GSD +acquiescence/SM +acquiescent/Y +acquirable +acquire/ASDG +acquirement/SM +acquisition's/A +acquisition/SM +acquisitive/PY +acquisitiveness/MS +acquit/S +acquittal/MS +acquittance/M +acquitted +acquitter/M +acquitting +acre/MS +acreage/MS +acrid/TPRY +acridity/MS +acridness/SM +acrimonious/YP +acrimoniousness/MS +acrimony/MS +acrobat/SM +acrobatic/S +acrobatically +acrobatics/M +acronym/SM +acrophobia/SM +acropolis/SM +across +acrostic/SM +acrylate/M +acrylic/S +act's +act/SADVG +acting/S +actinic +actinide/SM +actinium/MS +actinometer/MS +action's/IA +action/DMSGB +actions/AI +activate/AXCDSNGI +activated/U +activation/AMCI +activator/SM +active/APY +actively/I +activeness/MS +actives +activism/MS +activist/MS +activities/A +activity/MSI +actor/MAS +actress/SM +actual/SY +actuality/SM +actualization/MAS +actualize/GSD +actualizes/A +actuarial/Y +actuary/MS +actuate/GNXSD +actuation/M +actuator/SM +acuity/MS +acumen/SM +acupressure/S +acupuncture/SM +acupuncturist/S +acute/YTSRP +acuteness/MS +acyclic +acyclically +acyclovir/S +ad's +ad/AS +adage/MS +adagio/S +adamant/SY +adapt/SRDBZVG +adaptability/MS +adaptable/U +adaptation/MS +adapted/P +adaptedness/M +adapter/M +adapting/A +adaption +adaptive/U +adaptively +adaptiveness/M +adaptivity +add/ZGBSDR +addend/SM +addenda +addendum/M +adder/M +addict/SGVD +addiction/MS +addictive/P +addition/MS +additional/Y +additive/YMS +additivity +addle/GDS +address/MDRSZGB +addressability +addressable/U +addressed/A +addressee/SM +addresser/M +addresses/A +adduce/GRSD +adducer/M +adduct/DGVS +adduction/M +adductor/M +adenine/SM +adenoid/S +adenoidal +adept/RYPTS +adeptness/MS +adequacy/IMS +adequate/IPY +adequateness's/I +adequateness/SM +adhere/ZGRSD +adherence/SM +adherent/YMS +adherer/M +adhesion/MS +adhesive/PYMS +adhesiveness/MS +adiabatic +adiabatically +adieu/S +adipose/S +adiós +adj +adjacency/MS +adjacent/Y +adjectival/Y +adjective/MYS +adjoin/SDG +adjoint/M +adjourn/DGLS +adjournment/SM +adjudge/DSG +adjudicate/VNGXSD +adjudication/M +adjudicator/SM +adjudicatory +adjunct/VSYM +adjuration/SM +adjure/GSD +adjust/DRALGSB +adjustable/U +adjustably +adjusted/U +adjuster's/A +adjuster/SM +adjustive +adjustment/MAS +adjustor's +adjutant/SM +adman/M +admen +administer/GDJS +administrable +administrate/XSDVNG +administration/M +administrative/Y +administrator/MS +administratrix/M +admirable/P +admirableness/M +admirably +admiral/SM +admiralty/MS +admiration/MS +admire/RSDZBG +admirer/M +admiring/Y +admissibility/ISM +admissible/I +admissibly +admission/AMS +admit/AS +admittance/MS +admitted/A +admittedly +admitting/A +admix/SDG +admixture/SM +admonish/GLSRD +admonisher/M +admonishing/Y +admonishment/SM +admonition/MS +admonitory +ado/MS +adobe/MS +adolescence/MS +adolescent/SYM +adopt/RDSBZVG +adopted/AU +adopter/M +adoption/MS +adoptive/Y +adopts/A +adorable/P +adorableness/SM +adorably +adoration/SM +adore/DSRGZB +adorer/M +adoring/Y +adorn/SGLD +adorned/U +adornment/SM +adrenal/YS +adrenalin +adrenaline/MS +adrift +adroit/RTYP +adroitness/MS +ads +adsorb/GSD +adsorbate/M +adsorbent/S +adsorption/MS +adsorptive/Y +adulate/GNDSX +adulation/M +adulator/SM +adulatory +adult/MYPS +adulterant/SM +adulterate/NGSDX +adulterated/U +adulteration/M +adulterer/SM +adulteress/MS +adulterous/Y +adultery/SM +adulthood/MS +adultness/M +adumbrate/XSDVGN +adumbration/M +adumbrative/Y +adv +advance/DSRLZG +advancement/MS +advancer/M +advantage/GMEDS +advantageous/EY +advantageousness/M +advent/SVM +adventist/S +adventitious/PY +adventitiousness/M +adventive/Y +adventure/SRDGMZ +adventurer/M +adventuresome +adventuress/SM +adventurous/YP +adventurousness/SM +adverb/SM +adverbial/MYS +adversarial +adversary/SM +adverse/DSRPYTG +adverseness/MS +adversity/SM +advert/GSD +advertise/JGZSRDL +advertised/U +advertisement/SM +advertiser/M +advertising/M +advertorial/S +advice/SM +advisability/SIM +advisable/I +advisableness/M +advisably +advise/ZRSDGLB +advised/YU +advisedly/I +advisee/MS +advisement/MS +adviser/M +advisor's +advisor/S +advisory/S +advocacy/SM +advocate/NGVDS +advocation/M +advt +adz/MDSG +adze's +aegis/SM +aeolian +aeon's +aerate/XNGSD +aeration/M +aerator/MS +aerial/SMY +aerialist/MS +aerie/SRMT +aeroacoustic +aerobatic/S +aerobic/S +aerobically +aerodrome/SM +aerodynamic/S +aerodynamically +aerodynamics/M +aeronautic/S +aeronautical/Y +aeronautics/M +aerosol/MS +aerosolize/D +aerospace/SM +aesthete/S +aesthetic/U +aesthetically +aestheticism/MS +aesthetics/M +aether/M +aetiology/M +afar/S +affability/MS +affable/TR +affably +affair/SM +affect/EGSD +affectation/MS +affected/UEYP +affectedness/EM +affecter/M +affecting/Y +affection/EMS +affectionate/UY +affectioned +affectioning +affective/MY +afferent/YS +affiance/GDS +affidavit/SM +affiliate/EXSDNG +affiliated/U +affiliation/EM +affine +affinity/SM +affirm/ASDG +affirmation/SAM +affirmative/SY +affix/SDG +afflatus/MS +afflict/GVDS +affliction/SM +afflictive/Y +affluence/SM +affluent/YS +afford/DSBG +afforest/A +afforestation/SM +afforested +afforesting +afforests +affray/MDSG +affricate/VNMS +affrication/M +affricative/M +affright +affront/GSDM +afghan/MS +aficionado/MS +afield +afire +aflame +afloat +aflutter +afoot +afore +aforementioned +aforesaid +aforethought/S +afoul +afraid/U +afresh +afro +aft/ZR +afterbirth/M +afterbirths +afterburner/MS +aftercare/SM +aftereffect/MS +afterglow/MS +afterimage/MS +afterlife/M +afterlives +aftermath/M +aftermaths +aftermost +afternoon/SM +afters/M +aftershave/S +aftershock/SM +aftertaste/SM +afterthought/MS +afterward/S +afterworld/MS +again +against +agapae +agape/S +agar/MS +agate/SM +agave/SM +age/GJDRSMZ +aged/PY +agedness/M +ageism/S +ageist/S +ageless/YP +agelessness/MS +agency/SM +agenda/MS +agent/AMS +agented +agenting +agentive +ageratum/M +agglomerate/XNGVDS +agglomeration/M +agglutinate/VNGXSD +agglutination/M +agglutinin/MS +aggrandize/LDSG +aggrandizement/SM +aggravate/SDNGX +aggravating/Y +aggravation/M +aggregate/EGNVD +aggregated/U +aggregately +aggregateness/M +aggregates +aggregation/SM +aggregative/Y +aggression/SM +aggressive/U +aggressively +aggressiveness/S +aggressor/MS +aggrieve/GDS +aggrieved/Y +aghast +agile/YTR +agility/MS +agitate/XVNGSD +agitated/Y +agitation/M +agitator/SM +agitprop/MS +agleam +aglitter +aglow +agnostic/SM +agnosticism/MS +ago +agog +agonize/ZGRSD +agonized/Y +agonizedly/S +agonizing/Y +agony/SM +agoraphobia/MS +agoraphobic/S +agrarian/S +agrarianism/MS +agree/LEBDS +agreeable/EP +agreeableness/SME +agreeably/E +agreeing/E +agreement/ESM +agreer/S +agribusiness/SM +agricultural/Y +agriculturalist/S +agriculture/MS +agriculturist/SM +agrochemicals +agronomic/S +agronomist/SM +agronomy/MS +aground +ague/MS +ah +aha/S +ahead +ahem/S +ahoy/S +aid/ZGDRS +aide/MS +aided/U +aider/M +aigrette/SM +ail/LSDG +aileron/MS +ailment/SM +aim/ZSGDR +aimer/M +aimless/YP +aimlessness/MS +ain't +air/MDRTZGJS +airbag/MS +airbase/S +airborne +airbrush/SDMG +airbus/SM +aircraft/MS +aircrew/M +airdrop/MS +airdropped +airdropping +airfare/S +airfield/MS +airflow/SM +airfoil/MS +airframe/MS +airfreight/SGD +airhead/MS +airily +airiness/MS +airing/M +airless/P +airlessness/S +airlift/MDSG +airline/SRMZ +airliner/M +airlock/MS +airmail/DSG +airman/M +airmass +airmen +airpark +airplane/SM +airplay/S +airport/MS +airship/MS +airsick/P +airsickness/SM +airspace/SM +airspeed/SM +airstrip/MS +airtight/P +airtightness/M +airtime +airwaves +airway/SM +airworthiness/SM +airworthy/PTR +airy/PRT +aisle/DSGM +aitch/MS +ajar +aka +akimbo +akin +ala/MS +alabaster/MS +alack/S +alacrity/SM +alanine/M +alarm/SDG +alarming/Y +alarmist/MS +alas/S +alb/MS +alba/M +albacore/SM +albatross/SM +albedo/M +albeit +albinism/SM +albino/MS +album/MNXS +albumen/M +albumin/MS +albuminous +alchemical +alchemist/SM +alchemy/MS +alcohol/MS +alcoholic/MS +alcoholically +alcoholism/SM +alcove/MSD +aldehyde/M +alder/SM +alderman/M +aldermen +alderwoman +alderwomen +ale/MVS +aleatory +alee +alehouse/MS +alembic/SM +aleph/M +alert/STZGPRDY +alerted/Y +alertness/MS +alewife/M +alewives +alfalfa/MS +alfresco +alga/M +algae +algaecide +algal +algebra/MS +algebraic +algebraical/Y +algebraist/M +alginate/SM +algorithm/MS +algorithmic +algorithmically +alias/GSD +alibi/MDSG +alien/RDGMBS +alienable/IU +alienate/SDNGX +alienation/M +alienist/MS +alight/DSG +align/LASDG +aligned/U +aligner/SM +alignment/SAM +alike/U +alikeness/M +aliment/SDMG +alimentary +alimony/MS +alinement's +aliquot/S +alive/P +aliveness/MS +aliyah/M +aliyahs +alkali/M +alkalies +alkaline +alkalinity/MS +alkalize/SDG +alkaloid/MS +alkyd/S +alkyl/M +all-time +all/S +allay/GDS +allegation/SM +allege/SDG +alleged/Y +allegiance/SM +allegiant +allegoric +allegorical/YP +allegoricalness/M +allegorist/MS +allegory/SM +allegretto/MS +allegri +allegro/MS +allele/SM +alleluia/S +allemande/M +allergen/MS +allergenic +allergic +allergically +allergist/MS +allergy/MS +alleviate/SDVGNX +alleviation/M +alleviator/MS +alley/MS +alleyway/MS +alliance/MS +allier +allies/M +alligator/DMGS +alliterate/XVNGSD +alliteration/M +alliterative/Y +allocable/U +allocatable +allocate/ACSDNGX +allocated/U +allocation/AMC +allocative +allocator/AMS +allophone/MS +allophonic +allot/SDL +allotment/MS +allotments/A +allotrope/M +allotropic +allots/A +allotted/A +allotter/M +allotting/A +allover/S +allow/SBGD +allowable/P +allowableness/M +allowably +allowance/GSDM +allowed/Y +allowing/E +allows/E +alloy/SGMD +alloyed/U +allspice/MS +allude/GSD +allure/GLSD +allurement/SM +alluring/Y +allusion/MS +allusive/PY +allusiveness/MS +alluvial/S +alluvions +alluvium/MS +ally/ASDG +alma +almagest +almanac/MS +almightiness/M +almighty/P +almond/SM +almoner/MS +almost +alms/A +almshouse/SM +almsman/M +alnico +aloe/MS +aloft +aloha/SM +alone/P +aloneness/M +along +alongshore +alongside +aloof/YP +aloofness/MS +aloud +alp/MS +alpaca/SM +alpha/MS +alphabet/SGDM +alphabetic/S +alphabetical/Y +alphabetization/SM +alphabetize/SRDGZ +alphabetizer/M +alphanumeric/S +alphanumerical/Y +alpine/S +already +alright +also +alt/RZS +altar/MS +altarpiece/SM +alter/RDZBG +alterable/UI +alteration/MS +altercate/NX +altercation/M +altered/U +alternate/SDVGNYX +alternation/M +alternative/YMSP +alternativeness/M +alternator/MS +although +altimeter/SM +altitude/SM +alto/SM +altogether/S +altruism/SM +altruist/SM +altruistic +altruistically +alum/SM +alumina/SM +aluminum/MS +alumna/M +alumnae +alumni +alumnus/MS +alundum +alveolar/Y +alveoli +alveolus/M +alway/S +am/AS +amain +amalgam/MS +amalgamate/VNGXSD +amalgamation/M +amanuenses +amanuensis/M +amaranth/M +amaranths +amaretto/S +amaryllis/MS +amass/GRSD +amasser/M +amateur/SM +amateurish/YP +amateurishness/MS +amateurism/MS +amatory +amaze/LDSRGZ +amazed/Y +amazement/MS +amazing/Y +amazon/MS +amazonian +ambassador/MS +ambassadorial +ambassadorship/MS +ambassadress/SM +amber/MS +ambergris/SM +ambiance/MS +ambidexterity/MS +ambidextrous/Y +ambience's +ambient/S +ambiguity/MS +ambiguous/YP +ambiguously/U +ambiguousness/M +ambit/M +ambition/GMDS +ambitious/PY +ambitiousness/MS +ambivalence/SM +ambivalent/Y +amble/GZDSR +ambler/M +ambrose +ambrosia/SM +ambrosial/Y +ambulance/MS +ambulant/S +ambulate/DSNGX +ambulation/M +ambulatory/S +ambuscade/MGSRD +ambuscader/M +ambush/MZRSDG +ambusher/M +ameba's +ameliorate/XVGNSD +amelioration/M +amen/DRGTSB +amenability/SM +amenably +amend/SBRDGL +amended/U +amender/M +amendment/SM +amends/M +amenity/MS +amenorrhea/M +amerce/SDLG +amercement/MS +americanized +americium/MS +amethyst/MS +amethystine +amiability/MS +amiable/RPT +amiableness/M +amiably +amicability/SM +amicable/P +amicableness/M +amicably +amid/S +amide/SM +amidships +amidst +amigo/MS +amines +amino/M +aminobenzoic +amir's +amiss +amity/SM +ammeter/MS +ammo/MS +ammonia/MS +ammoniac +ammonium/M +ammunition/MS +amnesia/SM +amnesiac/MS +amnesic/S +amnesty/GMSD +amniocenteses +amniocentesis/M +amnion/SM +amniotic +amoeba/SM +amoebic +amoeboid +amok/MS +among +amongst +amontillado/MS +amoral/Y +amorality/MS +amorous/PY +amorousness/SM +amorphous/PY +amorphousness/MS +amortization/SUM +amortize/SDG +amortized/U +amount/SMRDZG +amour/MS +amp/SGMDY +amperage/SM +ampere/MS +ampersand/MS +amphetamine/MS +amphibian/SM +amphibious/PY +amphibiousness/M +amphibology/M +amphitheater/SM +amphora/M +amphorae +ample/PTR +ampleness/M +amplification/M +amplifier/M +amplify/DRSXGNZ +amplitude/MS +ampoule's +ampule/SM +amputate/DSNGX +amputation/M +amputee/SM +ams +amt +amuck's +amulet/SM +amuse/LDSRGVZ +amused/Y +amusement/SM +amuser/M +amusing/YP +amusingness/M +amyl/M +amylase/MS +an/CS +anabolic +anabolism/MS +anachronism/SM +anachronistic +anachronistically +anaconda/MS +anaerobe/SM +anaerobic +anaerobically +anaglyph/M +anagram/MS +anagrammatic +anagrammatically +anagrammed +anagramming +anal/Y +analgesia/MS +analgesic/S +analog/SM +analogical/Y +analogize/SDG +analogous/YP +analogousness/MS +analogue/SM +analogy/MS +analysand/MS +analyses +analysis/AM +analyst/SM +analytic/S +analytical/Y +analyticity/S +analytics/M +analyzable/U +analyze/DRSZGA +analyzed/U +analyzer/M +anamorphic +anapaest's +anapest/SM +anapestic/S +anaphora/M +anaphoric +anaphorically +anaplasmosis/M +anarchic +anarchical/Y +anarchism/MS +anarchist/MS +anarchistic +anarchy/MS +anastigmatic +anastomoses +anastomosis/M +anastomotic +anathema/MS +anathematize/GSD +anatomic +anatomical/YS +anatomist/MS +anatomize/GSD +anatomy/MS +ancestor/SMDG +ancestral/Y +ancestress/SM +ancestry/SM +anchor/SGDM +anchorage/SM +anchored/U +anchorite/MS +anchoritism/M +anchorman/M +anchormen +anchorpeople +anchorperson/S +anchorwoman +anchorwomen +anchovy/MS +ancient/SRYTP +ancientness/MS +ancillary/S +and/DZGS +andante/S +andiron/MS +androgen/SM +androgenic +androgynous +androgyny/SM +android/MS +anecdotal/Y +anecdote/SM +anechoic +anemia/SM +anemic/S +anemically +anemometer/MS +anemometry/M +anemone/SM +anent +aneroid +anesthesia/MS +anesthesiologist/MS +anesthesiology/SM +anesthetic/SM +anesthetically +anesthetist/MS +anesthetization/SM +anesthetize/ZSRDG +anesthetizer/M +aneurysm/MS +anew +angel/MDSG +angelfish/SM +angelic +angelica/MS +angelical/Y +anger/GDMS +angina/MS +angiography +angioplasty/S +angiosperm/MS +angle/GMZDSRJ +angler/M +angleworm/MS +anglicize/SDG +angling/M +angora/MS +angrily +angriness/M +angry/RTP +angst/MS +angstrom/MS +anguish/DSMG +angular/Y +angularity/MS +anhydride/M +anhydrite/M +anhydrous/Y +aniline/SM +animadversion/SM +animadvert/DSG +animal/MYPS +animalcule/MS +animate/YNGXDSP +animated/A +animatedly +animately/I +animateness/MI +animates/A +animating/A +animation/AMS +animator/SM +animism/SM +animist/S +animistic +animized +animosity/MS +animus/SM +anion/MS +anionic/S +anise/MS +aniseed/MS +aniseikonic +anisette/SM +anisotropic +anisotropy/MS +ankh/M +ankhs +ankle/GMDS +anklebone/SM +anklet/MS +annal/MNS +annalist/MS +anneal/DRSZG +annealer/M +annelid/MS +annex/GSD +annexation/SM +annexe/M +annihilate/XSDVGN +annihilation/M +annihilator/MS +anniversary/MS +annotate/VNGXSD +annotated/U +annotation/M +annotator/MS +announce/ZGLRSD +announced/U +announcement/SM +announcer/M +annoy/ZGSRD +annoyance/MS +annoyer/M +annoying/Y +annual/YS +annualized +annuitant/MS +annuity/MS +annul/SL +annular/YS +annuli +annulled +annulling +annulment/MS +annulus/M +annum +annunciate/XNGSD +annunciation/M +annunciator/SM +anode/SM +anodic +anodize/GDS +anodyne/SM +anoint/DRLGS +anointer/M +anointment/SM +anomalous/YP +anomalousness/M +anomaly/MS +anomic +anomie/M +anon/S +anonymity/MS +anonymous/YP +anonymousness/M +anopheles/M +anorak/SM +anorectic/S +anorexia/SM +anorexic/S +another/M +ans/M +answer/MZGBSDR +answerable/U +answered/U +answerer/M +ant/GSMD +antacid/MS +antagonism/MS +antagonist/MS +antagonistic +antagonistically +antagonize/GZRSD +antagonized/U +antagonizing/U +antarctic +ante/MS +anteater/MS +antebellum +antecedence/MS +antecedent/SMY +antechamber/SM +antedate/GDS +antediluvian/S +anteing +antelope/MS +antenatal +antenna/MS +antennae +anterior/SY +anteroom/SM +anthem/MGDS +anther/MS +anthill/S +anthologist/MS +anthologize/GDS +anthology/SM +anthraces +anthracite/MS +anthrax/M +anthropic +anthropocentric +anthropogenic +anthropoid/S +anthropological/Y +anthropologist/MS +anthropology/SM +anthropometric/S +anthropometry/M +anthropomorphic +anthropomorphically +anthropomorphism/SM +anthropomorphizing +anthropomorphous +anti/S +antiabortion +antiabortionist/S +antiaircraft +antibacterial/S +antibiotic/SM +antibody/MS +antic/MS +anticancer +anticipate/XVGNSD +anticipated/U +anticipation/M +anticipative/Y +anticipatory +anticked +anticking +anticlerical/S +anticlimactic +anticlimactically +anticlimax/SM +anticline/SM +anticlockwise +anticoagulant/S +anticoagulation/M +anticommunism/SM +anticommunist/SM +anticompetitive +anticyclone/MS +anticyclonic +antidemocratic +antidepressant/SM +antidisestablishmentarianism/M +antidote/DSMG +antifascist/SM +antiformant +antifreeze/SM +antifundamentalist/M +antigen/MS +antigenic +antigenicity/SM +antigone +antihero/M +antiheroes +antihistamine/MS +antihistorical +antiknock/MS +antilabor +antilogarithm/SM +antilogs +antimacassar/SM +antimalarial/S +antimatter/SM +antimicrobial/S +antimissile/S +antimony/SM +anting/M +antinomian +antinomy/M +antinuclear +antioxidant/MS +antiparticle/SM +antipasti +antipasto/MS +antipathetic +antipathy/SM +antipersonnel +antiperspirant/MS +antiphon/SM +antiphonal/SY +antipodal/S +antipode/MS +antipodean/S +antipollution/S +antipoverty +antiquarian/MS +antiquarianism/MS +antiquary/SM +antiquate/NGSD +antiquation/M +antique/MGDS +antiquity/SM +antiredeposition +antiresonance/M +antiresonator +antisemitic +antisemitism/M +antisepses +antisepsis/M +antiseptic/S +antiseptically +antiserum/SM +antislavery/S +antisocial/Y +antispasmodic/S +antisubmarine +antisymmetric +antisymmetry +antitank +antitheses +antithesis/M +antithetic +antithetical/Y +antithyroid +antitoxin/MS +antitrust/MR +antivenin/MS +antiviral/S +antivivisectionist/S +antiwar +antler/SDM +antonym/SM +antonymous +antral +antsy/RT +anus/SM +anvil/MDSG +anxiety/MS +anxious/PY +anxiousness/SM +any +anybody/S +anyhow +anymore +anyone/MS +anyplace +anything/S +anytime +anyway/S +anywhere/S +anywise +aorta/MS +aortic +apace +apache/MS +apart/LP +apartheid/SM +apartment/MS +apartness/M +apathetic +apathetically +apathy/SM +apatite/MS +ape/MDRSG +aped/A +apelike +aper/A +aperiodic +aperiodically +aperiodicity/M +aperitif/S +aperture/MDS +apex/MS +aphasia/SM +aphasic/S +aphelia +aphelion/SM +aphid/MS +aphonic +aphorism/MS +aphoristic +aphoristically +aphrodisiac/SM +apiarist/SM +apiary/SM +apical/YS +apices's +apiece +apish/YP +apishness/M +aplenty +aplomb/SM +apocalypse/MS +apocalyptic +apocrypha/M +apocryphal/YP +apocryphalness/M +apogee/MS +apolar +apolitical/Y +apologetic/S +apologetically/U +apologetics/M +apologia/SM +apologist/MS +apologize/GZSRD +apologizer/M +apologizes/A +apologizing/U +apology/MS +apoplectic +apoplexy/SM +apostasy/SM +apostate/SM +apostatize/DSG +apostle/SM +apostleship/SM +apostolic +apostrophe/SM +apostrophized +apothecary/MS +apothegm/MS +apotheoses +apotheosis/M +apotheosized +apotheosizes +apotheosizing +appall/SDG +appalling/Y +appaloosa/S +appanage/M +apparatus/SM +apparel/SGMD +apparency +apparent/U +apparently/I +apparentness/M +apparition/SM +appeal/SGMDRZ +appealer/M +appealing/UY +appear/AEGDS +appearance/AMES +appearer/S +appease/DSRGZL +appeased/U +appeasement/MS +appeaser/M +appellant/MS +appellate/VNX +appellation/M +appellative/MY +append/SGZDR +appendage/MS +appendectomy/SM +appendices +appendicitis/SM +appendix/SM +appertain/DSG +appetite/MVS +appetizer/SM +appetizing/YU +applaud/ZGSDR +applauder/M +applause/MS +apple/MS +applecart/M +applejack/MS +applesauce/SM +applet/S +appliance/SM +applicabilities +applicability/IM +applicable/I +applicably +applicant/MS +applicate/V +application/MA +applicative/Y +applicator/MS +applier/SM +appliqué/MSG +appliquéd +apply/AGSDXN +appoint/ELSADG +appointee/SM +appointer/MS +appointive +appointment/ASEM +apportion/GADLS +apportionment/SAM +appose/SDG +apposite/XYNVP +appositeness/MS +apposition/M +appositive/SY +appraisal/SAM +appraise/ZGDRS +appraised/A +appraisees +appraiser/M +appraises/A +appraising/Y +appreciable/I +appreciably/I +appreciate/XDSNGV +appreciated/U +appreciation/M +appreciative/PIY +appreciativeness/MI +appreciator/MS +appreciatory +apprehend/DRSG +apprehender/M +apprehensible +apprehension/SM +apprehensive/YP +apprehensiveness/SM +apprentice/DSGM +apprenticeship/SM +apprise/DSG +apprizer/SM +apprizingly +apprizings +approach/BRSDZG +approachability/UM +approachable/UI +approacher/M +approbate/NX +approbation/EMS +appropriable +appropriate/XDSGNVYTP +appropriated/U +appropriately/I +appropriateness/SMI +appropriation/M +appropriator/SM +approval/ESM +approve/DSREG +approved/U +approver's/E +approver/SM +approving/YE +approx +approximate/XGNVYDS +approximation/M +approximative/Y +appurtenance/MS +appurtenant/S +apricot/MS +apron/SDMG +apropos +apse/MS +apsis/M +apt/UPYI +apter +aptest +aptitude/SM +aptness's/U +aptness/SMI +aqua/SM +aquaculture/MS +aqualung/SM +aquamarine/SM +aquanaut/SM +aquaplane/GSDM +aquarium/MS +aquatic/S +aquatically +aquavit/SM +aqueduct/MS +aqueous/Y +aquiculture's +aquifer/SM +aquiline +arabesque/SM +arability/MS +arable/S +arachnid/MS +arachnoid/M +arachnophobia +arbiter/MS +arbitrage/GMZRSD +arbitrager/M +arbitrageur/S +arbitrament/MS +arbitrarily +arbitrariness/MS +arbitrary/P +arbitrate/SDXVNG +arbitration/M +arbitrator/SM +arbor/DMS +arboreal/Y +arbores +arboretum/MS +arborvitae/MS +arbutus/SM +arc/DSGM +arcade/SDMG +arcana/M +arcane/P +arch/PGVZTMYDSR +archaeological/Y +archaeologist/SM +archaic/P +archaically +archaism/SM +archaist/MS +archaize/GDRSZ +archaizer/M +archangel/SM +archbishop/SM +archbishopric/SM +archdeacon/MS +archdiocesan +archdiocese/SM +archduchess/MS +archduke/MS +archenemy/SM +archeologist's +archeology/MS +archer/M +archery/MS +archetypal +archetype/SM +archfiend/SM +archfool +archiepiscopal +arching/M +archipelago/SM +architect/MS +architectonic/S +architectonics/M +architectural/Y +architecture/SM +architrave/MS +archival +archive/DRSGMZ +archived/U +archivist/MS +archness/MS +archway/SM +arclike +arcsine +arctangent +arctic/S +ardency/M +ardent/Y +ardor/SM +arduous/YP +arduousness/SM +are/BS +area/SM +areal +areawide +aren't +arena/SM +arenaceous +argent/MS +arginine/MS +argon/MS +argonaut/S +argosy/SM +argot/SM +arguable/IU +arguably/IU +argue/DSRGZ +arguer/M +argument/SM +argumentation/SM +argumentative/YP +argumentativeness/MS +argyle/S +aria/SM +arid/TYRP +aridity/SM +aridness/M +aright +arise/GJSR +arisen +aristocracy/SM +aristocrat/MS +aristocratic +aristocratically +arithmetic/MS +arithmetical/Y +arithmetician/SM +arithmetize/SD +ark/MS +arm's +arm/ASEDG +armada/SM +armadillo/MS +armament's/E +armament/EAS +armature/MGSD +armband/SM +armchair/MS +armed/U +armer/MES +armful/SM +armhole/MS +arming/M +armistice/MS +armless +armlet/SM +armload/M +armor/ZRDMGS +armored/U +armorer/M +armorial/S +armory/DSM +armpit/MS +armrest/MS +army/SM +aroma/SM +aromatherapist/S +aromatherapy/S +aromatic/SP +aromatically +aromaticity/M +aromaticness/M +arose +around +arousal/MS +arouse/GSD +aroused/U +arpeggio/SM +arr/TV +arrack/M +arraign/SDGL +arraignment/MS +arrange/ZDSRLG +arrangeable/A +arranged/EA +arrangement/AMSE +arranger/M +arranges/EA +arranging/EA +arrant/Y +arras/SM +array/ESGMD +arrayer +arrear/SM +arrest/ADSG +arrestee/MS +arrester/MS +arresting/Y +arrestor/MS +arrhythmia/SM +arrhythmic +arrhythmical +arrival/MS +arrive/SRDG +arriver/M +arrogance/MS +arrogant/Y +arrogate/XNGDS +arrogation/M +arrow/SDMG +arrowhead/SM +arrowroot/MS +arroyo/MS +arsenal/MS +arsenate/M +arsenic/MS +arsenide/M +arsine/MS +arson/SM +arsonist/MS +art/SM +artefact's +arterial/SY +arteriolar +arteriole/SM +arterioscleroses +arteriosclerosis/M +artery/SM +artesian +artful/YP +artfulness/SM +arthritic/S +arthritides +arthritis/M +arthrogram/MS +arthropod/SM +arthroscope/S +arthroscopic +artichoke/SM +article/GMDS +articulable/I +articular +articulate/VGNYXPSD +articulated/EU +articulately/I +articulateness/IMS +articulates/I +articulation/M +articulator/SM +articulatory +artifact/MS +artifice/ZRSM +artificer/M +artificial/PY +artificiality/MS +artificialness/M +artillerist +artillery/SM +artilleryman/M +artillerymen +artiness/MS +artisan/SM +artist/MS +artiste/SM +artistic/I +artistically/I +artistry/SM +artless/YP +artlessness/MS +artsy/RT +artwork/MS +arty/TPR +arum/MS +as +asap +asbestos/MS +ascend/ADGS +ascendancy/MS +ascendant/SY +ascender/SM +ascension/SM +ascent/SM +ascertain/DSBLG +ascertainment/MS +ascetic/SM +ascetically +asceticism/MS +ascot/MS +ascribe/GSDB +ascription/MS +ascriptive +aseptic/S +aseptically +asexual/Y +asexuality/MS +ash/MNDRSG +ashame/D +ashamed/UY +ashcan/SM +ashlar/GSDM +ashman/M +ashore +ashram/SM +ashtray/MS +ashy/RT +aside/S +asinine/Y +asininity/MS +ask/DRZGS +askance +asked/U +asker/M +askew/P +aslant +asleep +asocial/S +asp/MNRXS +asparagus/MS +aspartame/S +aspect/SM +aspen/M +asper/M +asperity/SM +aspersion/SM +asphalt/MDRSG +asphodel/MS +asphyxia/MS +asphyxiate/GNXSD +asphyxiation/M +aspic/MS +aspidistra/MS +aspirant/MS +aspirate/NGDSX +aspiration/M +aspirational +aspirator/SM +aspire/GSRD +aspirer/M +aspirin/SM +asplenium +ass/MNS +assail/BGDS +assailable/U +assailant/SM +assassin/MS +assassinate/DSGNX +assassination/M +assault/SGVMDR +assaulter/M +assaultive/YP +assay/SZGRD +assayer/M +assemblage/MS +assemble/ADSREG +assembled/U +assembler/EMS +assemblies/A +assembly/EAM +assemblyman/M +assemblymen +assemblywoman +assemblywomen +assent/SGMRD +assert/ADGS +asserter/MS +assertion/AMS +assertional +assertive/PY +assertiveness/SM +assess/BLSDG +assessed/A +assesses/A +assessment/SAM +assessor/MS +asset/SM +asseverate/XSDNG +asseveration/M +asshole/MS! +assiduity/SM +assiduous/PY +assiduousness/SM +assign/ALBSGD +assignation/MS +assigned/U +assignee/MS +assigner/MS +assignment/MAS +assignor/MS +assigns/CU +assimilate/VNGXSD +assimilation/M +assimilationist/M +assist/RDGS +assistance/SM +assistant/SM +assistantship/SM +assisted/U +assister/M +assize/MGSD +assn +assoc +associable +associate/SDEXNG +associated/U +associateship +association/ME +associational +associative/Y +associativity/S +associator/MS +assonance/SM +assonant/S +assort/LRDSG +assorter/M +assortment/SM +asst +assuage/SDG +assuaged/U +assumability +assume/SRDBJG +assumer/M +assuming/UA +assumption/SM +assumptive +assurance/AMS +assure/AGSD +assured/PYS +assuredness/M +assurer/SM +assuring/YA +astatine/MS +aster/ESM +asteria +asterisk/SGMD +asterisked/U +astern +asteroid/SM +asteroidal +asthma/MS +asthmatic/S +astigmatic/S +astigmatism/SM +astir +astonish/GSDL +astonishing/Y +astonishment/SM +astound/SDG +astounding/Y +astraddle +astrakhan/SM +astral/SY +astray +astride +astringency/SM +astringent/YS +astrolabe/MS +astrologer/MS +astrological/Y +astrologist/M +astrology/SM +astronaut/SM +astronautic/S +astronautical +astronautics/M +astronomer/MS +astronomic +astronomical/Y +astronomy/SM +astrophysical +astrophysicist/SM +astrophysics/M +astute/RTYP +astuteness/MS +asunder +asylum/MS +asymmetric +asymmetrical/Y +asymmetry/MS +asymptomatic +asymptomatically +asymptote/MS +asymptotic/Y +asymptotically +asynchronism/M +asynchronous/Y +asynchrony +at +atavism/MS +atavist/MS +atavistic +ataxia/MS +ataxic/S +ate/S +atelier/SM +atemporal +atheism/SM +atheist/SM +atheistic +atheroscleroses +atherosclerosis/M +athirst +athlete/MS +athletic/S +athletically +athleticism/M +athletics/M +athwart +atilt +atlantes +atlas/SM +atmosphere/DSM +atmospheric/S +atmospherically +atoll/MS +atom/SM +atomic/S +atomically +atomicity/M +atomics/M +atomistic +atomization/SM +atomize/GZDRS +atomizer/M +atonal/Y +atonality/MS +atone/LDSG +atonement/SM +atop +atria +atrial +atrium/M +atrocious/YP +atrociousness/SM +atrocity/SM +atrophic +atrophy/DSGM +atropine/SM +attach/BLGZMDRS +attached/UA +attacher/M +attachment/ASM +attaché/S +attack/GBZSDR +attacker/M +attain/AGSD +attainabilities +attainability/UM +attainable/U +attainableness/M +attainably/U +attainder/MS +attained/U +attainer/MS +attainment/MS +attar/MS +attempt/ADSG +attempter/MS +attend/SGZDR +attendance/MS +attendant/SM +attended/U +attendee/SM +attender/M +attention/IMS +attentional +attentionality +attentive/YIP +attentiveness/IMS +attenuate/SDXGN +attenuated/U +attenuation/M +attenuator/MS +attest/GSDR +attestation/SM +attested/U +attester/M +attic/MS +attire/SDG +attitude/MS +attitudinal/Y +attitudinize/SDG +attn +attorney/SM +attract/BSDGV +attractant/SM +attraction/MS +attractive/UYP +attractiveness/UM +attractivenesses +attractor/MS +attributable/U +attribute/BVNGRSDX +attributed/U +attributer/M +attribution/M +attributional +attributive/SY +attrition/MS +attune/SDG +atty +atwitter +atypical/Y +aubergine/MS +auburn/SM +auction/MDSG +auctioneer/SDMG +audacious/PY +audaciousness/SM +audacity/MS +audibility/MSI +audible/I +audibles +audibly/I +audience/MS +audio/SM +audiogram/SM +audiological +audiologist/MS +audiology/SM +audiometer/MS +audiometric +audiometry/M +audiophile/SM +audiotape/S +audiovisual/S +audit/SMDVG +audited/U +audition/MDSG +auditor/MS +auditorium/MS +auditory/S +auger/SM +aught/S +augment/DRZGS +augmentation/SM +augmentative/S +augmenter/M +augur/GDMS +augury/SM +august/STPYR +augustness/SM +auk/MS +aunt/MYS +auntie/MS +aunty's +aura/SM +aural/Y +aureole/GMSD +aureomycin +auric +auricle/SM +auricular +aurora/SM +auroral +auscultate/XDSNG +auscultation/M +auspice/SM +auspicious/IPY +auspiciousness/IM +auspiciousnesses +austere/TYRP +austereness/M +austerity/SM +austral +australes +australites +authentic/UI +authentically +authenticate/GNDSX +authenticated/U +authentication/M +authenticator/MS +authenticity/MS +author/DMGS +authoress/S +authorial +authoritarian/S +authoritarianism/MS +authoritative/PY +authoritativeness/SM +authority/SM +authorization/MAS +authorize/AGDS +authorized/U +authorizer/SM +authorizes/U +authorship/MS +autism/MS +autistic/S +auto/SDMG +autobahn/MS +autobiographer/MS +autobiographic +autobiographical/Y +autobiography/MS +autoclave/SDGM +autocollimator/M +autocorrelate/GNSDX +autocorrelation/M +autocracy/SM +autocrat/SM +autocratic +autocratically +autodial/R +autodidact/MS +autofluorescence +autograph/MDG +autographs +autoignition/M +autoimmune +autoimmunity/S +autoloader +automaker/S +automata's +automate/NGDSX +automatic/S +automatically +automation/M +automatism/SM +automatize/DSG +automaton/SM +automobile/GDSM +automorphism/SM +automotive +autonavigator/SM +autonomic/S +autonomous/Y +autonomy/MS +autopilot/SM +autopsy/MDSG +autoregressive +autorepeat/GS +autostart +autosuggestibility/M +autotransformer/M +autoworker/S +autumn/MS +autumnal/Y +aux +auxiliary/S +auxin/MS +av/ZR +avail/BSZGRD +availability/USM +available/U +availableness/M +availably +availing/U +avalanche/MGSD +avant +avarice/SM +avaricious/PY +avariciousness/M +avast/S +avatar/MS +avaunt/S +avdp +ave/S +avenge/ZGSRD +avenged/U +avenger/M +avenue/MS +average/DSPGYM +averred +averrer +averring +avers/V +averse/YNXP +averseness/M +aversion/M +avert/GSD +aves/C +avg +avian/S +aviary/SM +aviate/NX +aviation/M +aviator/SM +aviatrices +aviatrix/SM +avid/TPYR +avidity/MS +avionic/S +avionics/M +avitaminoses +avitaminosis/M +avocado/MS +avocation/SM +avocational +avoid/ZRDBGS +avoidable/U +avoidably/U +avoidance/SM +avoider/M +avoirdupois/MS +avouch/GDS +avow/GEDS +avowal/EMS +avowed/Y +avower/M +avuncular +aw/GD +await/SDG +awake/GS +awaken/SADG +awakened/U +awakener/M +awakening/S +award/RDSZG +awarder/M +aware/TRP +awareness/MSU +awash +away/PS +awe/SM +aweigh +awesome/PY +awesomeness/SM +awestruck +awful/YP +awfuller +awfullest +awfulness/SM +awhile/S +awkward/PRYT +awkwardness/MS +awl/MS +awn/MDJGS +awning/DM +awoke +awoken +awry/RT +ax/DRSZGM +axehead/S +axeman +axial/Y +axillary +axiological/Y +axiology/M +axiom/SM +axiomatic/S +axiomatically +axiomatization/MS +axiomatize/GDS +axion/SM +axis/SM +axle/MS +axletree/MS +axolotl/SM +axon/SM +ayah/M +ayahs +ayatollah +ayatollahs +aye/MZRS +azalea/SM +azimuth/M +azimuthal/Y +azimuths +azure/MS +b/KGD +baa/SDG +babbitt/GDS +babble/RSDGZ +babbler/M +babe/SM +babel/S +baboon/MS +babushka/MS +baby/TDSRMG +babyhood/MS +babyish +babysat +babysit/S +babysitter/S +babysitting +baccalaureate/MS +baccarat/SM +bacchanal/SM +bacchanalia +bacchanalian/S +bachelor/SM +bachelorhood/SM +bacillary +bacilli +bacillus/MS +back/GZDRMSJ +backache/SM +backarrow +backbench/ZR +backbencher/M +backbit/ZGJR +backbite/S +backbiter/M +backbitten +backboard/SM +backbone/SM +backbreaking +backchaining +backcloth/M +backdate/GDS +backdrop/MS +backdropped +backdropping +backed/U +backer/M +backfield/SM +backfill/SDG +backfire/GDS +backgammon/MS +background/SDRMZG +backhand/RDMSZG +backhanded/Y +backhander/M +backhoe/S +backing/M +backlash/GRSDM +backless +backlog/MS +backlogged +backlogging +backorder +backpack/ZGSMRD +backpacker/M +backpedal/DGS +backplane/MS +backplate/SM +backrest/MS +backscatter/SMDG +backseat/S +backside/SM +backslapper/MS +backslapping/M +backslash/DSG +backslid/RZG +backslide/S +backslider/M +backspace/GSD +backspin/SM +backstabber/M +backstabbing +backstage +backstair/S +backstitch/GDSM +backstop/MS +backstopped +backstopping +backstreet/M +backstretch/SM +backstroke/GMDS +backtalk/S +backtrack/SDRGZ +backup/SM +backward/YSP +backwardness/MS +backwash/SDMG +backwater/SM +backwood/S +backwoodsman/M +backwoodsmen +backyard/MS +bacon/SRM +baconer/M +bacteria/MS +bacterial/Y +bactericidal +bactericide/SM +bacteriologic +bacteriological +bacteriologist/MS +bacteriology/SM +bacterium/M +bad/PSNY +badder +baddest +baddie/MS +bade +badge/DSRGMZ +badger/DMG +badinage/DSMG +badland/S +badman/M +badmen +badminton/MS +badmouth/DG +badmouths +badness/SM +baffle/RSDGZL +bafflement/MS +baffler/M +baffling/Y +bag/SM +bagatelle/MS +bagel/SM +bagful/MS +baggage/SM +baggageman +baggagemen +bagged/M +bagger/SM +baggily +bagginess/MS +bagging/M +baggy/PRST +bagpipe/RSMZ +bagpiper/M +baguette/SM +bah +bahs +bail/GSMYDRB +bailiff/SM +bailiwick/MS +bailout/MS +bailsman/M +bailsmen +bairn/SM +bait/GSMDR +baiter/M +baize/GMDS +bake/ZGJDRS +baked/U +bakehouse/M +baker/M +bakery/SM +bakeshop/S +baking/M +baklava/M +baksheesh/SM +balaclava/MS +balalaika/MS +balance's +balance/USDG +balanced/A +balancedness +balancer/MS +balboa/SM +balcony/MSD +bald/PYDRGST +balderdash/MS +baldfaced +baldness/MS +baldric/SM +baldy +bale/MZGDRS +baleen/MS +baleful/YP +balefuller +balefullest +balefulness/MS +baler/M +balk/GDRS +balkanization +balkanize/DG +balker/M +balkiness/M +balky/PRT +ball/GZMSDR +ballad/SM +ballade/MS +balladeer/MS +balladry/MS +ballast/SGMD +ballcock/S +baller/M +ballerina/MS +ballet/MS +balletic +ballfields +ballgame/S +ballistic/S +ballistics/M +balloon/RDMZGS +balloonist/S +ballot/MRDGS +balloter/M +ballpark/SM +ballplayer/SM +ballpoint/SM +ballroom/SM +ballsy/TR +ballyhoo/SGMD +balm/MS +balminess/SM +balmy/PRT +baloney/SM +balsa/MS +balsam/GMDS +balsamic +baluster/MS +balustrade/SM +bamboo/SM +bamboozle/GSD +ban/SGMD +banal/TYR +banality/MS +banana/SM +band's +band/EDGS +bandage/RSDMG +bandager/M +bandanna/SM +bandbox/MS +bandeau/M +bandeaux +bander/M +banding/M +bandit/MS +banditry/MS +bandmaster/MS +bandoleer/SM +bandpass +bandsman/M +bandsmen +bandstand/SM +bandstop +bandwagon/MS +bandwidth/M +bandwidths +bandy/TGRSD +bane/MS +baneful/Y +banefuller +banefullest +bang/GDRZMS +banger/M +bangkok +bangle/MS +bani +banish/RSDGL +banisher/M +banishment/MS +banister/MS +banjo/MS +banjoist/SM +bank/GZJDRMBS +bankbook/SM +bankcard/S +banker/M +banking/M +banknote/S +bankroll/DMSG +bankrupt/DMGS +bankruptcy/MS +banned/U +banner/SDMG +banning/U +bannister's +bannock/SM +banns +banquet/SZGJMRD +banqueter/M +banquette/MS +bans/U +banshee/MS +bantam/MS +bantamweight/MS +banter/RDSG +banterer/M +bantering/Y +banyan/MS +banzai/S +baobab/SM +baptism/SM +baptismal/Y +baptist/MS +baptistery/MS +baptistry's +baptize/SRDZG +baptized/U +baptizer/M +baptizes/U +bar/TGMDRS +barb/DRMSGZ +barbarian/MS +barbarianism/MS +barbaric +barbarically +barbarism/MS +barbarity/SM +barbarize/SDG +barbarous/PY +barbarousness/M +barbecue/DRSMG +barbed/P +barbel/MS +barbell/SM +barbeque's +barber/DMG +barbered/U +barberry/MS +barbershop/MS +barbital/M +barbiturate/MS +barbwire/SM +barcarole/SM +bard/MDSG +bardic +bare/YSP +bareback/D +barefaced/YP +barefacedness/M +barefoot/D +barehanded +bareheaded +barelegged +bareness/MS +barf/YDSG +barfly/SM +bargain/ZGSDRM +bargainer/M +barge/DSGM +bargeman/M +bargemen +bargepole/M +barhop/S +barhopped +barhopping +baritone/MS +barium/MS +bark/GZDRMS +barked/C +barkeep/SRZ +barkeeper/M +barker/M +barks/C +barley/MS +barleycorn/MS +barmaid/SM +barman/M +barmen +barn/GDSM +barnacle/MDS +barnful +barnsful +barnstorm/DRGZS +barnstormer/M +barnyard/MS +barometer/MS +barometric +barometrically +baron/SM +baronage/MS +baroness/MS +baronet/MS +baronetcy/SM +baronial +barony/SM +baroque/SPMY +barque's +barrack/SDRG +barracker/M +barracuda/MS +barrage/MGSD +barre/GMDSJ +barred/ECU +barrel/SGMD +barren/SPRT +barrenness/SM +barrette/SM +barricade/SDMG +barrier/MS +barring/R +barrio/SM +barrister/MS +barroom/SM +barrow/MS +bars/ECU +barstool/SM +bartend/ZR +bartender/M +barter/SRDZG +barterer/M +barycenter +barycentre's +barycentric +baryon/SM +bas/DRSTG +basal/Y +basalt/SM +basaltic +base's +base/CGRSDL +baseball/MS +baseband +baseboard/MS +baseless +baseline/SM +basely +baseman/M +basemen +basement/CSM +baseness/MS +baseplate/M +basetting +bash/JGDSR +bashful/PY +bashfulness/MS +basic/S +basically +basil/MS +basilar +basilica/SM +basilisk/SM +basin/DMS +basinful/S +basis/M +bask/GSD +basket/SM +basketball/MS +basketry/MS +basketwork/SM +basophilic +bass/SM +basset/GMDS +bassinet/SM +bassist/MS +basso/MS +bassoon/MS +bassoonist/MS +basswood/SM +bast/SGZMDR +bastard/MYS +bastardization/MS +bastardize/SDG +bastardized/U +bastardy/MS +baste/NXS +baster/M +basting/M +bastion/DM +bat/SMDRG +batch/MRSDG +bate/KGSADC +bated/U +bater/AC +bath/JMDSRGZ +bathe +bather/M +bathetic +bathhouse/SM +bathmat/S +bathos/SM +bathrobe/MS +bathroom/SDM +baths +bathtub/MS +bathwater +bathyscaphe's +bathysphere/MS +batik/DMSG +batiste/SM +batman/M +batmen +baton/SM +batsman/M +batsmen +battalion/MS +batted +batten/SDMG +batter/SRDZG +battery/MS +batting/MS +battle/GMZRSDL +battledore/MS +battledress +battlefield/SM +battlefront/SM +battleground/SM +battlement/SMD +battler/M +battleship/MS +batty/RT +batwings +bauble/SM +baud/M +baulk/GSDM +bauxite/SM +bawd/SM +bawdily +bawdiness/MS +bawdy/PRST +bawl/SGDR +bawler/M +bay/GSMDY +bayberry/MS +bayonet/SGMD +bayou/MS +bazaar/MS +bazillion/S +bazooka/MS +bbl +bdrm +be/KS +beach/MSDG +beachcomber/SM +beachhead/SM +beachwear/M +beacon/DMSG +bead/SJGMD +beading/M +beadle/SM +beadsman/M +beadworker +beady/TR +beagle/SDGM +beak/ZSDRM +beaker/M +beam/MDRSGZ +bean/DRMGZS +beanbag/SM +beanie/SM +beanpole/MS +beanstalk/SM +bear/ZBRSJG +bearable/U +bearably/U +beard/DSGM +bearded/P +beardless +bearer/M +bearing/M +bearish/PY +bearishness/SM +bearlike +bearskin/MS +beast/SJMY +beasties +beastings/M +beastliness/MS +beastly/PTR +beat/NRGSBZJ +beatable/U +beatably/U +beaten/U +beater/M +beatific +beatifically +beatification/M +beatify/GNXDS +beating/M +beatitude/MS +beatnik/SM +beau/MS +beaut/SM +beauteous/YP +beauteousness/M +beautician/MS +beautification/M +beautifier/M +beautiful/PTYR +beautifully/U +beautifulness/M +beautify/SRDNGXZ +beauty/SM +beaux's +beaver/DMSG +bebop/MS +becalm/GDS +became +because +beck/GSDM +beckon/SDG +becloud/SGD +become/GJS +becoming/UY +bed/MS +bedaub/GDS +bedazzle/GLDS +bedazzlement/SM +bedbug/SM +bedchamber/M +bedclothes +bedded +bedder/MS +bedding/MS +bedeck/DGS +bedevil/DGLS +bedevilment/SM +bedfast +bedfellow/MS +bedim/S +bedimmed +bedimming +bedizen/DGS +bedlam/MS +bedlinen +bedmaker/SM +bedmate/MS +bedpan/SM +bedpost/SM +bedraggle/GSD +bedridden +bedrock/SM +bedroll/SM +bedroom/DMS +bedsheets +bedside/MS +bedsit +bedsitter/M +bedsore/MS +bedspread/SM +bedspring/SM +bedstead/SM +bedstraw/M +bedtime/SM +bee/MZGJRS +beebread/MS +beech/MRSN +beechnut/MS +beechwood +beef/GZSDRM +beefburger/SM +beefcake/MS +beefiness/MS +beefsteak/MS +beefy/TRP +beehive/MS +beekeeper/MS +beekeeping/SM +beeline/MGSD +been/S +beep/GZSMDR +beeper/M +beer/M +beermat/S +beery/TR +beeswax/DSMG +beet/SM +beetle/GMRSD +beetroot/M +beeves/M +befall/SGN +befell +befit/SM +befitted +befitting/Y +befog/S +befogged +befogging +before +beforehand +befoul/GSD +befriend/DGS +befuddle/GLDS +befuddlement/SM +beg/S +began +beget/S +begetting +beggar/DYMSG +beggarliness/M +beggarly/P +beggary/MS +begged +begging +begin/S +beginner/MS +beginning/MS +begone/S +begonia/SM +begot +begotten +begrime/SDG +begrudge/GDRS +begrudging/Y +beguile/RSDLZG +beguilement/SM +beguiler/M +beguiling/Y +beguine/SM +begum/MS +begun +behalf/M +behalves +behave/GRSD +behavior/SMD +behavioral/Y +behaviorism/MS +behaviorist/S +behavioristic/S +behead/GSD +beheld +behemoth/M +behemoths +behest/SM +behind/S +behindhand +behold/ZGRNS +beholder/M +behoofs +behoove/SDJMG +behooving/YM +beige/MS +being/M +bejewel/SDG +belabor/MDSG +belate/D +belated/PY +belatedness/M +belay/GSD +belch/GSD +beleaguer/GDS +belfry/SM +belie +belief/ESUM +belier/M +believability's +believability/U +believable/U +believably/U +believe/EZGDRS +believed/U +believer/MUSE +believing/U +belittle/RSDGL +belittlement/MS +belittler/M +bell/GSMD +belladonna/MS +bellboy/MS +belle/MS +belled/A +belletrist/SM +belletristic +bellflower/M +bellhop/MS +bellicose/YP +bellicoseness/M +bellicosity/MS +belligerence/SM +belligerency/MS +belligerent/SMY +belling/A +bellman/M +bellmen +bellow/DGS +bellows/M +bells/A +bellwether/MS +belly/SDGM +bellyache/SRDGM +bellyacher/M +bellybutton/MS +bellyful/MS +bellyfull +belong/DGJS +belonging/MP +belove/D +beloved/S +below/S +belt/GSMD +belted/U +belting/M +beltway/SM +beluga/SM +belvedere/M +bely/DSRG +beman +bemire/SDG +bemoan/GDS +bemuse/GSDL +bemused/Y +bemusement/SM +bench/MRSDG +bencher/M +benchmark/GDMS +bend/BUSG +bended +bender/MS +beneath +benediction/MS +benedictory +benefaction/MS +benefactor/MS +benefactress/S +benefice/MGSD +beneficence/SM +beneficent/Y +beneficial/PY +beneficialness/M +beneficiary/MS +benefit/SRDMZG +benefiter/M +benevolence/SM +benevolent/YP +benevolentness/M +benighted/YP +benightedness/M +benign/Y +benignant +benignity/MS +bent/U +bents +bentwood/SM +benumb/SGD +benzene/MS +benzine/SM +bequeath/GSD +bequeaths +bequest/MS +berate/GSD +bereave/GLSD +bereavement/MS +bereft +beret/SM +berg/NRSM +beribbon/D +beriberi/SM +berkelium/SM +berm/SM +berry/SDMG +berrylike +berserk/SR +berserker/M +berth/MDGJ +berths +beryl/SM +beryllium/MS +bes +beseech/RSJZG +beseecher/M +beseeching/Y +beseem/GDS +beset/S +besetting +beside/S +besiege/SRDZG +besieger/M +besmear/GSD +besmirch/GSD +besom/GMDS +besot/S +besotted +besotting +besought +bespangle/GSD +bespatter/SGD +bespeak/SG +bespectacled +bespoke +bespoken +best/DRSG +bestial/Y +bestiality/MS +bestiary/MS +bestir/S +bestirred +bestirring +bestow/SGD +bestowal/SM +bestrew/DGS +bestrewn +bestridden +bestride/SG +bestrode +bestseller/MS +bestselling +bestubble/D +bet/MS +beta/SM +betake/SG +betaken +betatron/M +betcha +betel/MS +beth/M +bethel/M +bethink/GS +bethought +betide/GSD +betimes +betoken/GSD +betook +betray/SRDZG +betrayal/SM +betrayer/M +betroth/GD +betrothal/SM +betrothed/U +betroths +better/SDLG +betterment/MS +betting +bettor/SM +between/SP +betweenness/M +betwixt +bevel/SJGMRD +beverage/MS +bevy/SM +bewail/GDS +beware/GSD +bewhisker/D +bewigged +bewilder/LDSG +bewildered/PY +bewildering/Y +bewilderment/SM +bewitch/LGDS +bewitching/Y +bewitchment/SM +bey/MS +beyond/S +bezel/MS +bf +bi/M +biannual/Y +bias/DSMPG +biased/U +biathlon/MS +biaxial/Y +bib/MS +bibbed +bibbing +bible/MS +biblical/Y +biblicists +bibliographer/MS +bibliographic/S +bibliographical/Y +bibliography/MS +bibliophile/MS +bibulous +bicameral +bicameralism/MS +bicarb/MS +bicarbonate/MS +bicentenary/S +bicentennial/S +bicep/S +biceps/M +bichromate/DM +bicker/SRDZG +bickerer/M +bickering/M +biconcave +biconnected +biconvex +bicuspid/S +bicycle/RSDMZG +bicycler/M +bicyclist/SM +bid/GMRS +biddable +bidden/U +bidder/MS +bidding/MS +biddy/SM +bide/S +bider/M +bidet/SM +bidiagonal +bidirectional/Y +bids/A +biennial/SY +biennium/SM +bier/M +bifocal/S +bifurcate/SDXGNY +bifurcation/M +big/PYS +bigamist/SM +bigamous +bigamy/SM +bigged +bigger +biggest +biggie/SM +bigging +biggish +bighead/MS +bighearted/P +bigheartedness/S +bighorn/MS +bight/SMDG +bigmouth/M +bigmouths +bigness/SM +bigot/MDSG +bigoted/Y +bigotry/MS +bigwig/MS +biharmonic +bijection/MS +bijective/Y +bijou/M +bijoux +bike/MZGDRS +biker/M +bikini/SMD +bilabial/S +bilateral/PY +bilateralness/M +bilayer/S +bilberry/MS +bile/SM +bilge/GMDS +biliary +bilinear +bilingual/SY +bilingualism/SM +bilious/P +biliousness/SM +bilk/GZSDR +bilker/M +bill/JGZSBMDR +billboard/MDGS +biller/M +billet/MDGS +billfold/MS +billiard/SM +billing/M +billingsgate/SM +billion/SHM +billionaire/MS +billionths +billow/DMGS +billowy/RT +billposters +billy/SM +bimbo/MS +bimetallic/S +bimetallism/MS +bimodal +bimolecular/Y +bimonthly/S +bin/SM +binary/S +binaural/Y +bind/JDRGZS +binder/M +bindery/MS +binding/MPY +bindingness/M +bindle/M +binds/AU +bindweed/MS +bing/GNDM +binge/MS +bingo/MS +binnacle/MS +binned +binning +binocular/SY +binodal +binomial/SYM +binuclear +biochemical/SY +biochemist/MS +biochemistry/MS +biodegradability/S +biodegradable +biodiversity/S +bioengineering/M +bioethics +biofeedback/SM +biog/S +biograph/RZ +biographer/M +biographic +biographical/Y +biography/MS +biol +biologic/S +biological/SY +biologist/SM +biology/MS +biomass/SM +biomedical +biomedicine/M +biometric/S +biometrics/M +biometry/M +biomolecule/S +biomorph +bionic/S +bionically +bionics/M +biophysic/S +biophysical/Y +biophysicist/SM +biophysics/M +biopic/S +biopsy/SDGM +biorhythm/S +bioscience/S +biosphere/MS +biostatistic/S +biosynthesized +biotechnological +biotechnologist +biotechnology/SM +biotic +biotin/SM +bipartisan +bipartisanship/MS +bipartite/YN +bipartition/M +biped/MS +bipedal +biplane/MS +bipolar +bipolarity/MS +biracial +birch/MRSDNG +bird/SMDRGZ +birdbath/M +birdbaths +birdbrain/SDM +birdcage/SM +birder/M +birdhouse/MS +birdie/MSD +birdieing +birdlike +birdlime/MGDS +birdseed/MS +birdsong +birdtables +birdwatch/GZR +birefringence/M +birefringent +biretta/SM +birth's/A +birth/MDG +birthday/SM +birthmark/MS +birthplace/SM +birthrate/MS +birthright/MS +births/A +birthstone/SM +bis +biscuit/MS +bisect/DSG +bisection/MS +bisector/MS +biserial +bisexual/YMS +bisexuality/MS +bishop/DGSM +bishopric/SM +bismuth/M +bismuths +bison/M +bisque/SM +bistable +bistate +bistro/SM +bisyllabic +bit's/C +bit/MRJSZG +bitblt/S +bitch/MSDG +bitchily +bitchiness/MS +bitchy/PTR +bite/S +biter/M +biting/Y +bitmap/SM +bits/C +bitser/M +bitted +bitten +bitter/PSRDYTG +bittern/SM +bitterness/SM +bitternut/M +bitterroot/M +bittersweet/YMSP +bitting +bitty/PRT +bitumen/MS +bituminous +bitwise +bivalent/S +bivalve/MSD +bivariate +bivouac/MS +bivouacked +bivouacking +biweekly/S +biyearly +biz/M +bizarre/YSP +bizarreness/M +bizzes +bk +bl/D +blab/S +blabbed +blabber/GMDS +blabbermouth/M +blabbermouths +blabbing +black/SJTXPYRDNG +blackamoor/SM +blackball/SDMG +blackberry/GMS +blackbird/SGDRM +blackbirder/M +blackboard/SM +blackbody/S +blackcurrant/M +blacken/GDR +blackener/M +blackguard/MDSG +blackhead/SM +blacking/M +blackish +blackjack/SGMD +blackleg/M +blacklist/DRMSG +blackmail/DRMGZS +blackmailer/M +blackness/MS +blackout/SM +blacksmith/MG +blacksmiths +blacksnake/MS +blackspot +blackthorn/MS +blacktop/MS +blacktopped +blacktopping +bladder/MS +bladdernut/M +bladderwort/M +blade/DSGM +blah/MDG +blahs +blame/DSRBGMZ +blameless/YP +blamelessness/SM +blamer/M +blameworthiness/SM +blameworthy/P +blanc/M +blanch/DRSG +blancher/M +blancmange/SM +bland/PYRT +blandish/SDGL +blandishment/MS +blandness/MS +blank/SPGTYRD +blanket/SDRMZG +blanketing/M +blankness/MS +blare/DSG +blarney/DMGS +blaspheme/RSDZG +blasphemer/M +blasphemous/PY +blasphemousness/M +blasphemy/SM +blast/SMRDGZ +blaster/M +blasting/M +blastoff/SM +blasé +blatancy/SM +blatant/YP +blather/DRGS +blatting +blaze/DSRGMZ +blazer/M +blazing/Y +blazon/SGDR +blazoner/M +bldg +bleach/DRSZG +bleached/U +bleacher/M +bleak/TPYRS +bleakness/MS +blear/GDS +blearily +bleariness/SM +bleary/PRT +bleat/RDGS +bleater/M +bleed/ZRJSG +bleeder/M +bleep/GMRDZS +blemish/DSMG +blemished/U +blench/DSG +blend/GZRDS +blender/M +bless/JGSD +blessed/PRYT +blessedness/MS +blessing/M +blew +blight/GSMDR +blighter/M +blimey/S +blimp/MS +blind/JGTZPYRDS +blinded/U +blinder/M +blindfold/SDG +blinding/MY +blindness/MS +blindside/SDG +blink/RDGSZ +blinker/MDG +blinking/U +blinks/M +blintz/SM +blintze/M +blip/MS +blipped +blipping +bliss/SDMG +blissful/PY +blissfulness/MS +blister/SMDG +blistering/Y +blistery +blithe/TYPR +blitheness/SM +blither/G +blithesome +blitz/GSDM +blitzkrieg/SM +blizzard/MS +bloat/SRDGZ +bloater/M +blob/MS +blobbed +blobbing +bloc/MS +block's +block/USDG +blockade/ZMGRSD +blockader/M +blockage/MS +blockbuster/SM +blockbusting/MS +blocker/MS +blockhead/MS +blockhouse/SM +blocky/R +blog +blog's +blogged +blogger +blogging +blogging's +blogs +bloke/SM +blond/SPMRT +blonde's +blondish +blondness/MS +blood/SMDG +bloodbath +bloodbaths +bloodcurdling +bloodhound/SM +bloodied/U +bloodiness/MS +bloodless/PY +bloodlessness/SM +bloodletting/MS +bloodline/SM +bloodmobile/MS +bloodroot/M +bloodshed/SM +bloodshot +bloodsport/S +bloodstain/MDS +bloodstock/SM +bloodstone/M +bloodstream/SM +bloodsucker/SM +bloodsucking/S +bloodthirstily +bloodthirstiness/MS +bloodthirsty/RTP +bloodworm/M +bloody/TPGDRS +bloodymindedness +bloom/SMRDGZ +bloomer/M +bloop/GSZRD +blooper/M +blossom/DMGS +blossomy +blot/MS +blotch/GMDS +blotchy/RT +blotted +blotter/MS +blotting +blotto +blouse/GMSD +blow/GZRS +blower/M +blowfish/M +blowfly/MS +blowgun/SM +blowing/M +blown/U +blowout/MS +blowpipe/SM +blowtorch/SM +blowup/MS +blowy/RST +blowzy/RT +blubber/GSDR +blubbery +bludgeon/GSMD +blue/JMYTGDRSP +blueback +bluebell/MS +blueberry/SM +bluebill/M +bluebird/MS +bluebonnet/SM +bluebook/M +bluebottle/MS +bluebush +bluefish/SM +bluegill/SM +bluegrass/MS +blueing's +blueish +bluejacket/MS +bluejeans +blueness/MS +bluenose/MS +bluepoint/SM +blueprint/GDMS +bluer/M +bluest/M +bluestocking/SM +bluesy/TR +bluet/MS +bluff/SPGTZYRD +bluffer/M +bluffness/MS +bluing/M +bluish/P +bluishness/M +blunder/GSMDRJZ +blunderbuss/MS +blunderer/M +blundering/Y +blunt/PSGTYRD +bluntness/MS +blur/MS +blurb/GSDM +blurred/Y +blurriness/S +blurring/Y +blurry/RPT +blurt/GSRD +blush/RSDGZ +blusher/M +blushing/UY +bluster/SDRZG +blusterer/M +blustering/Y +blusterous +blustery +blvd +boa/SM +boar/MS +board's +board/IS +boarded +boarder/SM +boardgames +boarding/SM +boardinghouse/SM +boardroom/MS +boardwalk/SM +boast/SJRDGZ +boaster/M +boastful/YP +boastfulness/MS +boat/MDRGZJS +boatclubs +boater/M +boathouse/SM +boating/M +boatload/SM +boatman/M +boatmen +boatswain/SM +boatyard/SM +bob/SM +bobbed +bobbin/MS +bobbing/M +bobble/SDGM +bobby/SM +bobbysoxer's +bobcat/MS +bobolink/SM +bobs/M +bobsled/MS +bobsledded +bobsledder/MS +bobsledding/M +bobsleigh/M +bobsleighs +bobtail/SGDM +bobwhite/SM +boccie/SM +bock/GDS +bockwurst +bod/SGMD +bode/S +bodega/MS +bodhisattva +bodice/SM +bodied/M +bodiless +bodily +boding/M +bodkin/SM +body/DSMG +bodybuilder/SM +bodybuilding/S +bodyguard/MS +bodying/M +bodysuit/S +bodyweight +bodywork/SM +bog/MS +bogey/SGMD +bogeyman/M +bogeymen +bogged +bogging +boggle/SDG +boggling/Y +boggy/RT +bogie's +bogus +bogy's +bogyman +bogymen +bohemian/S +bohemianism/S +boil/JSGZDR +boiled/AU +boiler/M +boilermaker/MS +boilerplate/SM +boils/A +boisterous/YP +boisterousness/MS +bola/SM +bold/YRPST +boldface/SDMG +boldness/MS +bole/MS +bolero/MS +bolivar/MS +bolivares +boll/MDSG +bollard/SM +bollix/GSD +bolo/MS +bologna/MS +bolometer/MS +boloney's +bolster/SRDG +bolsterer/M +bolt/MDRGS +bolted/U +bolter/M +bolts/U +bolus/SM +bomb/SGZDRJ +bombard/LDSG +bombardier/MS +bombardment/SM +bombast/RMS +bombastic +bombastically +bomber/M +bombproof +bombshell/SM +bona +bonanza/MS +bonbon/SM +bond/JMDRSGZ +bondage/SM +bonder/M +bondholder/SM +bondman/M +bondmen +bonds/A +bondsman/M +bondsmen +bondwoman/M +bondwomen +bone/MZDRSG +boned/U +bonehead/SDM +boneless +boner/M +bonfire/MS +bong/GDMS +bongo/MS +bonhomie/MS +boniness/MS +bonito/MS +bonjour +bonkers +bonnet/SGMD +bonneted/U +bonnie +bonny/RT +bonsai/SM +bonus/SM +bony/RTP +bonzes +boo/GSDH +boob/DMSG +booby/SM +boodle/GMSD +boogeyman's +boogie/SD +boogieing +boohoo/GDS +book/GZDRMJSB +bookbind/JRGZ +bookbinder/M +bookbindery/SM +bookbinding/M +bookcase/MS +booked/U +bookend/SGD +bookie/SM +booking/M +bookish/PY +bookishness/M +bookkeep/GZJR +bookkeeper/M +bookkeeping/M +booklet/MS +bookmaker/MS +bookmaking/MS +bookmark/MDGS +bookmobile/MS +bookplate/SM +bookseller/SM +bookshelf/M +bookshelves +bookshop/MS +bookstall/MS +bookstore/SM +bookwork/M +bookworm/MS +boolean/S +boom/DRGJS +boomer/M +boomerang/MDSG +boomtown/S +boon/MS +boondocks +boondoggle/DRSGZ +boondoggler/M +boonies +boor/MS +boorish/PY +boorishness/SM +boost/SGZMRD +booster/M +boosterism +boot's +boot/AGDS +bootblack/MS +bootee/MS +booth/M +booths +bootie's +bootlaces +bootleg/S +bootlegged/M +bootlegger/SM +bootlegging/M +bootless +bootprints +bootstrap/SM +bootstrapped +bootstrapping +booty/SM +booze/DSRGMZ +boozer/M +boozy/TR +bop/S +bopped +bopping +borate/MSD +borax/MS +bordello/MS +border/JRDMGS +borderer/M +borderland/SM +borderline/MS +bore/ZGJDRS +boredom/MS +boreholes +borer/M +boric +boring/YMP +born/AIU +borne/U +boron/SM +borosilicate/M +borough/M +boroughs +borrow/JZRDGBS +borrower/M +borrowing/M +borscht/SM +borstal/MS +borzoi/MS +bosh/MS +bosom's +bosom/SGUD +bosomy/RT +boson/SM +boss/DSRMG +bossily +bossiness/MS +bossism/MS +bossy/PTSR +bosun's +bot/S +botanic/S +botanical/SY +botanist/SM +botany/SM +botch/SRDGZ +botcher/M +botfly/M +both/ZR +bother/DG +bothersome +bothy/M +bottle/GMZSRD +bottleneck/GSDM +bottler/M +bottom/SMRDG +bottomless/YP +bottomlessness/M +bottommost +botulin/M +botulinus/M +botulism/SM +boudoir/MS +bouffant/S +bougainvillea/SM +bough/MD +boughs +bought/N +bouillabaisse/MS +bouillon/MS +boulder/GMDS +boulevard/MS +bounce/SRDGZ +bouncer/M +bouncily +bouncing/Y +bouncy/TRP +bound/AUDI +boundary/MS +bounded/UP +boundedness/MU +bounden +bounder/AM +bounders +bounding +boundless/YP +boundlessness/SM +bounds/IA +bounteous/PY +bounteousness/MS +bountiful/PY +bountifulness/SM +bounty/SDM +bouquet/SM +bourbon/SM +bourgeois/M +bourgeoisie/SM +bout/MS +boutique/MS +boutonnière/MS +bovine/YS +bow/SZGNDR +bowdlerization/MS +bowdlerize/GRSD +bowed/U +bowel/GMDS +bower/DMG +bowie +bowing/M +bowl/GZSMDR +bowlder's +bowleg/SM +bowlegged +bowler/M +bowlful/S +bowline/MS +bowling/M +bowman/M +bowmen +bows/R +bowser/M +bowsprit/SM +bowstring/GSMD +bowwow/DMGS +box/DRSJZGM +boxcar/SM +boxer/M +boxful/M +boxing/M +boxlike +boxtops +boxwood/SM +boxy/TPR +boy/MRS +boycott/RDGS +boycotter/M +boyfriend/MS +boyhood/SM +boyish/PY +boyishness/MS +boyscout +boysenberry/SM +bozo/SM +bpi +bps +bra/MS +brace/DSRJGM +braced/U +bracelet/MS +bracer/M +brachia +brachium/M +bracken/SM +bracket/SGMD +bracketed/U +bracketing/M +brackish/P +brackishness/SM +bract/SM +brad/SM +bradawl/M +bradded +bradding +brae/SM +brag/S +braggadocio/SM +braggart/SM +bragged +bragger/MS +braggest +bragging +braid/RDSJG +braider/M +braiding/M +braille/DSG +brain/GSDM +braincell/S +brainchild/M +brainchildren +braininess/MS +brainless/YP +brainlessness/M +brainpower/M +brainstorm/DRMGJS +brainstorming/M +brainteaser/S +brainteasing +brainwash/JGRSD +brainwasher/M +brainwashing/M +brainwave/S +brainy/RPT +braise/SDG +brake/DSGM +brakeman/M +brakemen/M +bramble/DSGM +brambling/M +brambly/RT +bran/SM +branch/MDSJG +branched/U +branching/M +branchlike +brand/SMRDGZ +branded/U +brander/GDM +brandish/GSD +brandy/GDSM +brandywine +branned +branning +brash/PYSRT +brashness/MS +brass/GSDM +brasserie/SM +brassiere/MS +brassily +brassiness/SM +brassy/RSPT +brat/SM +bratty/RT +bratwurst/MS +bravado/M +bravadoes +brave/DSRGYTP +braveness/MS +bravery/MS +bravest/M +bravo/SDG +bravura/SM +brawl/MRDSGZ +brawler/M +brawn/MS +brawniness/SM +brawny/TRP +bray/SDRG +brayer/M +braze/GZDSR +brazen/PYDSG +brazenness/MS +brazer/M +brazier/SM +breach/MDRSGZ +breacher/M +bread/SMDHG +breadbasket/SM +breadboard/SMDG +breadbox/S +breadcrumb/S +breadfruit/MS +breadline/MS +breadth/M +breadths +breadwinner/MS +break/SZRBG +breakable/U +breakables +breakage/MS +breakaway/MS +breakdown/MS +breaker/M +breakfast/RDMGZS +breakfaster/M +breakfront/S +breaking/M +breakneck +breakout/MS +breakpoint/SMDG +breakthrough/SM +breakthroughs +breakup/SM +breakwater/SM +bream/SDG +breast/MDSG +breastbone/MS +breastfed +breastfeed/G +breasting/M +breastplate/SM +breaststroke/SM +breastwork/MS +breath/ZBJMDRSG +breathable/U +breathalyser/S +breathe +breather/M +breathing/M +breathless/PY +breathlessness/SM +breaths +breathtaking/Y +breathy/TR +bred/DG +bredes +breech/MDSG +breeching/M +breed/SZJRG +breeder's +breeder/I +breeding/IM +breeds/I +breeze/GMSD +breezeway/SM +breezily +breeziness/SM +breezy/RPT +bremsstrahlung/M +brethren +breve/SM +brevet/MS +brevetted +brevetting +breviary/SM +brevity/MS +brew/DRGZS +brewer/M +brewery/MS +brewing/M +brewpub/S +briar's +bribe/GZDSR +briber/M +bribery/MS +brick/GRDSM +brickbat/SM +bricklayer/MS +bricklaying/SM +brickmason/S +brickwork/SM +brickyard/M +bridal/S +bride/MS +bridegroom/MS +bridesmaid/MS +bridge/SDGM +bridgeable/U +bridged/U +bridgehead/MS +bridgework/MS +bridging/M +bridle/SDGM +bridled/U +bridleway/S +brief/YRDJPGTS +briefcase/SM +briefed/C +briefing/M +briefness/MS +briefs/C +brier/MS +brig/SM +brigade/GDSM +brigadier/MS +brigand/MS +brigandage/MS +brigantine/MS +bright/GXTPSYNR +brighten/RDZG +brightener/M +brightness/SM +brilliance/MS +brilliancy/MS +brilliant/PSY +brilliantine/MS +brilliantness/M +brim/SM +brimful +brimless +brimmed +brimming +brimstone/MS +brindle/DSM +brine/GMDSR +briner/M +bring/RGZS +bringer/M +brininess/MS +brink/MS +brinkmanship/SM +briny/PTSR +brioche/SM +briquet's +briquette/MGSD +brisk/YRDPGTS +brisket/SM +briskness/MS +bristle/DSGM +bristly/TR +bristol/S +britches +brittle/YTPDRSG +brittleness/MS +bro/SH +broach/DRSG +broacher/M +broad/TXSYRNP +broadband +broadcast/RSGZJ +broadcaster/M +broadcasts/A +broadcloth/M +broadcloths +broaden/JGRDZ +broadleaved +broadloom/SM +broadminded/P +broadness/S +broadsheet/MS +broadside/SDGM +broadsword/MS +brocade/DSGM +broccoli/MS +brochette/SM +brochure/SM +brogan/MS +brogue/MS +broil/RDSGZ +broiler/M +broke/RGZ +broken/YP +brokenhearted/Y +brokenness/MS +broker/DMG +brokerage/MS +bromide/MS +bromidic +bromine/MS +bronc/S +bronchi/M +bronchial +bronchiolar +bronchiole/MS +bronchiolitis +bronchitic/S +bronchitis/MS +broncho's +bronchus/M +bronco/SM +broncobuster/SM +brontosaur/SM +brontosaurus/SM +bronze/SRDGM +bronzed/M +bronzing/M +brooch/MS +brood/SMRDGZ +brooder/M +broodiness/M +brooding/Y +broodmare/SM +broody/PTR +brook/SGDM +brooklet/MS +brookside +broom/SMDG +broomstick/MS +bros/S +broth/ZMR +brothel/MS +brother/DYMG +brotherhood/SM +brotherliness/MS +brotherly/P +broths +brougham/MS +brought +brouhaha/MS +brow/MS +browbeat/NSG +brown/YRDMSJGTP +brownie/MTRS +browning/M +brownish +brownness/MS +brownout/MS +brownstone/MS +brows/SRDGZ +browse +browser/M +brr +brucellosis/M +bruin/MS +bruise/JGSRDZ +bruised/U +bruiser/M +bruit/DSG +brunch/MDSG +brunet/S +brunette/SM +brunt/GSMD +brush/MSRDG +brusher/M +brushfire/MS +brushlike +brushoff/S +brushwood/SM +brushwork/MS +brushy/R +brusque/PYTR +brusqueness/MS +brutal/Y +brutality/SM +brutalization/SM +brutalize/SDG +brutalized/U +brutalizes/AU +brute/DSRGM +brutish/YP +brutishness/SM +bu +bub/MS +bubble/RSDGM +bubblegum/S +bubbler/M +bubbly/TRS +bubo/M +buboes +bubonic +buccaneer/GMDS +buck/GSDRM +buckaroo/SM +buckboard/SM +bucker/M +bucket/SGMD +bucketful/MS +buckeye/SM +buckhorn/M +buckle/RSDGMZ +buckled/U +buckler/MDG +buckles/U +buckling's +buckling/U +buckram/GSDM +bucksaw/SM +buckshot/MS +buckskin/SM +buckteeth +bucktooth/DM +buckwheat/SM +bucolic/S +bucolically +bud/MS +budded +budding/S +buddy/GSDM +budge/GDS +budgerigar/MS +budget/GMRDZS +budgetary +budgeter/M +budgie/MS +budging/U +buff's +buff/ASGD +buffalo/MDG +buffaloes +buffer/RDMSGZ +buffered/U +bufferer/M +buffet/GMDJS +bufflehead/M +buffoon/SM +buffoonery/MS +buffoonish +bug's +bug/CS +bugaboo/SM +bugbear/SM +bugeyed +bugged/C +bugger/SCM! +buggered +buggering +buggery/M +bugging/C +buggy/RSMT +bugle/GMDSRZ +bugler/M +build/SAG +builder/SM +building/SM +buildup/MS +built/AUI +bulb/DMGS +bulblet +bulbous +bulge/DSGM +bulgy/RT +bulimarexia/S +bulimia/MS +bulimic/S +bulk/GDRMS +bulkhead/SDM +bulkiness/SM +bulky/RPT +bull/MDGS +bulldog/SM +bulldogged +bulldogger +bulldogging +bulldoze/GRSDZ +bulldozer/M +bullet/GMDS +bulletin/SGMD +bulletproof/SGD +bullfight/SJGZMR +bullfighter/M +bullfighting/M +bullfinch/MS +bullfrog/SM +bullhead/DMS +bullheaded/YP +bullheadedness/SM +bullhide +bullhorn/SM +bullied/M +bullion/SM +bullish/PY +bullishness/SM +bullock/MS +bullpen/MS +bullring/SM +bullseye +bullshit/MS! +bullshitted/! +bullshitter/S! +bullshitting/! +bullwhackers +bully/TRSDGM +bullyboy/MS +bullying/M +bulrush/SM +bulwark/GMDS +bum/SM +bumble/JGZRSD +bumblebee/MS +bumbler/M +bumbling/Y +bummed/M +bummer/MS +bummest +bumming/M +bump/GZDRS +bumper/DMG +bumpiness/MS +bumpkin/MS +bumptious/PY +bumptiousness/SM +bumpy/PRT +bun/SM +bunch/MSDG +bunchy/RT +bunco's +buncombe's +bundle/GMRSD +bundled/U +bundler/M +bung/GDMS +bungalow/MS +bungee/SM +bunghole/MS +bungle/GZRSD +bungler/M +bungling/Y +bunion/SM +bunk's +bunk/CSGDR +bunker's/C +bunker/SDMG +bunkhouse/SM +bunkmate/MS +bunko's +bunkum/SM +bunny/SM +bunt/GJZDRS +bunting/M +buoy/SMDG +buoyancy/MS +buoyant/Y +bur/MYS +burble/RSDG +burbler/M +burbs +burden's +burden/UGDS +burdensome/PY +burdensomeness/M +burdock/SM +bureau/MS +bureaucracy/MS +bureaucrat/MS +bureaucratic/U +bureaucratically +bureaucratization/MS +bureaucratize/SDG +burg/SZRM +burgeon/GDS +burger/M +burgess/MS +burgh/MRZ +burgher/M +burghs +burglar/SM +burglarize/GDS +burglarproof/DGS +burglary/MS +burgle/SDG +burgomaster/SM +burgundy/S +burial/ASM +buried/U +burier/M +burl/SMDRG +burlap/MS +burler/M +burlesque/SRDMYG +burlesquer/M +burley/M +burliness/SM +burly/PRT +burn/GZSDRBJ +burnable/S +burned/U +burner/M +burning/Y +burnish/GDRSZ +burnisher/M +burnoose/MS +burnout/MS +burnt/YP +burp/SGMD +burr/GSDRM +burrito/S +burro/SM +burrow/GRDMZS +burrower/M +bursa/M +bursae +bursar/MS +bursary/MS +bursitis/MS +burst/SRG +burster/M +bury/ASDG +bus's/A +bus/GMDSJ +busboy/MS +busby/SM +buses/A +busgirl/S +bush/JMDSRG +bushel/MDJSG +bushiness/MS +bushing/M +bushland +bushman/M +bushmaster/SM +bushmen +bushwhack/RDGSZ +bushwhacker/M +bushwhacking/M +bushy/PTR +busily +business/MS +businesslike +businessman/M +businessmen +businesspeople +businessperson/S +businesswoman/M +businesswomen +busk/GRM +busker/M +buskin/SM +buss/D +bust/MSDRGZ +bustard/MS +buster/M +bustle/GSD +bustling/Y +busty/RT +busy/DSRPTG +busybody/MS +busyness/MS +busywork/SM +but/ACS +butane/MS +butch/RSZ +butcher/MDRYG +butcherer/M +butchery/MS +butene/M +butler/SDMG +butt/SGZMDR +butte/MS +butted/A +butter/RDMGZ +butterball/MS +buttercup/SM +buttered/U +butterfat/MS +butterfingered +butterfingers/M +butterfly/MGSD +buttermilk/MS +butternut/MS +butterscotch/SM +buttery/TRS +butting/M +buttock/SGMD +button's +button/SUDG +buttoner/M +buttonhole/GMRSD +buttonholer/M +buttonweed +buttonwood/SM +buttress/MSDG +butyl/M +butyrate/M +buxom/TPYR +buxomness/M +buy/ZGRS +buyback/S +buyer/M +buyout/S +buzz/DSRMGZ +buzzard/MS +buzzer/M +buzzword/SM +buzzy +bx +bxs +by/ZR +bye/MZS +byelaw's +bygone/S +bylaw/SM +byline/RSDGM +byliner/M +bypass/GSDM +bypath/M +bypaths +byplay/S +byproduct/SM +byre/SM +byroad/MS +bystander/SM +byte/SM +byway/SM +byword/SM +byzantine +c/B +ca +cab/SMR +cabal/SM +cabala/MS +caballed +caballero/SM +caballing +cabana/MS +cabaret/SM +cabbage/MGSD +cabbed +cabbing +cabby's +cabdriver/SM +caber/M +cabin/GDMS +cabinet/MS +cabinetmaker/SM +cabinetmaking/MS +cabinetry/SM +cabinetwork/MS +cable/GMDS +cablecast/SG +cablegram/SM +cabochon/MS +caboodle/SM +caboose/MS +cabriolet/MS +cabstand/MS +cacao/SM +cacciatore +cache/DSRGM +cachepot/MS +cachet/MDGS +cackle/RSDGZ +cackler/M +cackly +cacophonist +cacophonous +cacophony/SM +cacti +cactus/M +cad/SM +cadaver/SM +cadaverous/Y +caddish/PY +caddishness/SM +caddy/GSDM +cadence/CSM +cadenced +cadencing +cadent/C +cadenza/MS +cadet/SM +cadge/DSRGZ +cadger/M +cadmium/MS +cadre/SM +caducei +caduceus/M +caesura/SM +cafeteria/SM +caffeine/SM +caftan/SM +café/MS +cage/MZGDRS +caged/U +cager/M +cagey/P +cagier +cagiest +cagily +caginess/MS +cahoot/MS +caiman's +cairn/SDM +caisson/SM +caitiff/MS +cajole/LGZRSD +cajolement/MS +cajoler/M +cajolery/SM +cake/MGDS +cakewalk/SMDG +cal/C +calabash/SM +calaboose/MS +calamari/S +calamine/GSDM +calamitous/YP +calamitousness/M +calamity/MS +calcareous/PY +calcareousness/M +calciferous +calcification/M +calcify/XGNSD +calcimine/GMSD +calcine/SDG +calcite/SM +calcium/SM +calculability/IM +calculable/IP +calculate/AXNGDS +calculated/PY +calculating/U +calculatingly +calculation/AM +calculative +calculator/SM +calculi +calculus/M +caldera/SM +caldron's +calendar/MDGS +calender/MDGS +calf/M +calfskin/SM +caliber/SM +calibrate/XNGSD +calibrated/U +calibrater's +calibrating/A +calibration/M +calibrator/MS +calico/M +calicoes +calif's +californium/SM +caliper/SDMG +caliph/M +caliphate/SM +caliphs +calisthenic/S +calisthenics/M +call/AGRDBS +calla/MS +callback/S +called/U +callee/M +caller/MS +calligraph/RZ +calligrapher/M +calligraphic +calligraphist/MS +calligraphy/MS +calling/SM +calliope/SM +callisthenics's +callosity/MS +callous/PGSDY +callousness/SM +callow/RTSP +callowness/MS +callus/SDMG +calm/PGTYDRS +calming/Y +calmness/MS +caloric/S +calorie/SM +calorific +calorimeter/MS +calorimetric +calorimetry/M +calumet/MS +calumniate/NGSDX +calumniation/M +calumniator/SM +calumnious +calumny/MS +calvary/M +calve/GDS +calves/M +calyces's +calypso/SM +calyx/MS +cam/MS +camaraderie/SM +camber/DMSG +cambial +cambium/SM +cambric/MS +camcorder/S +came/N +camel/SM +camelhair's +camellia/MS +cameo/GSDM +camera/MS +camerae +cameraman/M +cameramen +camerawoman +camerawomen +camion/M +camisole/MS +cammed +camomile's +camouflage/DRSGZM +camouflager/M +camp's +camp/SCGD +campaign/ZMRDSG +campaigner/M +campanile/SM +campanological +campanologist/SM +campanology/MS +camper/SM +campesinos +campest +campfire/SM +campground/MS +camphor/MS +camping/S +campsite/MS +campus/GSDM +campy/RT +camshaft/SM +can't +can/MDRSZGJ +canal/SGMD +canalization/MS +canalize/GSD +canapé/S +canard/MS +canary/SM +canasta/SM +cancan/SM +cancel/RDZGS +cancelate/D +canceled/U +canceler/M +cancellation/MS +cancer/MS +cancerous/Y +candelabra/S +candelabrum/M +candid/TRYPS +candidacy/MS +candidate/SM +candidature/S +candidly/U +candidness/SM +candle/GMZRSD +candlelight/SMR +candlelit +candlepower/SM +candler/M +candlestick/SM +candlewick/MS +candor/MS +candy/GSDM +cane/SM +canebrake/SM +caner/M +canine/S +caning/M +canister/SGMD +canker/SDMG +cankerous +cannabis/MS +canned +cannelloni +canner/SM +cannery/MS +cannibal/SM +cannibalism/MS +cannibalistic +cannibalization/SM +cannibalize/GSD +cannily/U +canniness/UM +canninesses +canning/M +cannister/SM +cannon/SDMG +cannonade/SDGM +cannonball/SGDM +cannot +canny/RPUT +canoe/DSGM +canoeist/SM +canon/SM +canonic +canonical/SY +canonicalization +canonicalize/GSD +canonist/M +canonization/MS +canonize/SDG +canonized/U +canopy/GSDM +canst +cant's +cant/CZGSRD +cantabile/S +cantaloupe/MS +cantankerous/PY +cantankerousness/SM +cantata/SM +canted/IA +canteen/MS +canter/CM +cantered +cantering +canticle/SM +cantilever/SDMG +canto/MS +canton/MGSLD +cantonal +cantonment/SM +cantor/MS +cants/A +canvas/RSDMG +canvasback/MS +canvass/RSDZG +canvasser/M +canyon/MS +cap/MDRSZB +capability/ISM +capable/PI +capableness/IM +capabler +capablest +capably/I +capacious/PY +capaciousness/MS +capacitance/SM +capacitate/V +capacitive/Y +capacitor/MS +capacity/IMS +caparison/SDMG +cape/SM +caper/GDM +capeskin/SM +capillarity/MS +capillary/S +capita/M +capital/SMY +capitalism/SM +capitalist/SM +capitalistic +capitalistically +capitalization/SMA +capitalize/RSDGZ +capitalized/AU +capitalizer/M +capitalizes/A +capitation/CSM +capitol/SM +capitulate/AXNGSD +capitulation/MA +caplet/S +capo/SM +capon/SM +capped/UA +capping/M +cappuccino/MS +caprice/MS +capricious/PY +capriciousness/MS +caps/AU +capsicum/MS +capsize/SDG +capstan/MS +capstone/MS +capsular +capsule/MGSD +capsulize/GSD +capt/V +captain/SGDM +captaincy/MS +caption/GSDRM +captious/PY +captiousness/SM +captivate/XGNSD +captivation/M +captivator/SM +captive/MS +captivity/SM +captor/SM +capture/AGSD +capturer/MS +car/ZGSMDR +caracul's +carafe/SM +caramel/MS +caramelize/SDG +carapace/SM +carapaxes +carat/SM +caravan/DRMGS +caravaner/M +caravansary/MS +caravanserai's +caravel/MS +caraway/MS +carbide/MS +carbine/MS +carbohydrate/MS +carbolic +carbon/MS +carbonaceous +carbonate/SDXMNG +carbonation/M +carbonic +carboniferous +carbonization/SAM +carbonize/ZGRSD +carbonizer's +carbonizer/AS +carbonizes/A +carbonyl/M +carborundum +carboy/MS +carbuncle/SDM +carbuncular +carburetor/MS +carburetter/S +carburettor/SM +carcase/MS +carcass/SM +carcinogen/SM +carcinogenic +carcinogenicity/MS +carcinoma/SM +card's +card/EDRSG +cardamom/MS +cardboard/MS +carder's/E +carder/MS +cardholders +cardiac/S +cardigan/SM +cardinal/SYM +cardinality/SM +carding/M +cardiogram/MS +cardiograph/M +cardiographs +cardioid/M +cardiologist/SM +cardiology/MS +cardiomegaly/M +cardiopulmonary +cardiovascular +cardsharp/ZSMR +care/S +cared/U +careen/DSG +career/SGRDM +careerism/M +careerist/MS +carefree +careful/PY +carefuller +carefullest +carefulness/MS +caregiver/S +careless/YP +carelessness/MS +carer/M +caress/SRDMVG +caresser/M +caressing/Y +caressive/Y +caret/SM +caretaker/SM +careworn +carfare/MS +cargo/M +cargoes +carhop/SM +carhopped +carhopping +caribou/MS +caricature/GMSD +caricaturisation +caricaturist/MS +caricaturization +caries/M +carillon/SM +carillonned +carillonning +caring/U +carious +carjack/GSJDRZ +carload/MSG +carmine/MS +carnage/MS +carnal/Y +carnality/SM +carnation/IMS +carnelian/SM +carney's +carnival/MS +carnivore/SM +carnivorous/YP +carnivorousness/MS +carny/SDG +carob/SM +carol/SGZMRD +caroler/M +carom/GSMD +carotene/MS +carotid/MS +carousal/MS +carouse/SRDZG +carousel/MS +carouser/M +carp/MDRSGZ +carpal/SM +carpel/SM +carpenter/DSMG +carpentering/M +carpentry/MS +carper/M +carpet/MDJGS +carpetbag/MS +carpetbagged +carpetbagger/MS +carpetbagging +carpeting/M +carpi/M +carping/Y +carpool/DGS +carport/MS +carpus/M +carrageen/M +carrel/SM +carriage/SM +carriageway/SM +carrier/M +carrion/SM +carrot/MS +carroty/RT +carrousel's +carry/RSDZG +carryall/MS +carryout/S +carryover/S +carsick/P +carsickness/SM +cart/MDRGSZ +cartage/MS +carte/M +cartel/SM +carter/M +carthorse/MS +cartilage/MS +cartilaginous +cartload/MS +cartographer/MS +cartographic +cartography/MS +carton/GSDM +cartoon/GSDM +cartoonist/MS +cartridge/SM +cartwheel/MRDGS +carve/DSRJGZ +carven +carver/M +carving/M +caryatid/MS +casaba/SM +casbah/M +cascade/MSDG +cascara/MS +case/DSJMGL +casebook/SM +cased/U +caseharden/SGD +casein/SM +caseload/MS +casement/SM +casework/ZMRS +caseworker/M +cash/GZMDSR +cashbook/SM +cashew/MS +cashier/SDMG +cashless +cashmere/MS +casing/M +casino/MS +cask/GSDM +casket/SGMD +cassava/MS +casserole/MGSD +cassette/SM +cassia/MS +cassino's +cassock/SDM +cassowary/SM +cast/GZSJMDR +castanet/SM +castaway/SM +caste/MHS +castellated +caster/M +castigate/XGNSD +castigation/M +castigator/SM +casting/M +castle/GMSD +castoff/S +castor's +castrate/DSNGX +castration/M +casts/A +casual/SYP +casualness/SM +casualty/SM +casuist/MS +casuistic +casuistry/SM +cat/SMRZ +cataclysm/MS +cataclysmal +cataclysmic +catacomb/MS +catafalque/SM +catalepsy/MS +cataleptic/S +catalog/SDRMZG +cataloger/M +catalpa/SM +catalysis/M +catalyst/SM +catalytic +catalytically +catalyze/DSG +catamaran/MS +catapult/MGSD +cataract/MS +catarrh/M +catarrhs +catastrophe/SM +catastrophic +catastrophically +catatonia/MS +catatonic/S +catbird/MS +catboat/SM +catcall/SMDG +catch/BRSJLGZ +catchable/U +catchall/MS +catcher/M +catchment/SM +catchpenny/S +catchphrase/S +catchup/MS +catchword/MS +catchy/TR +catechism/MS +catechist/SM +catechize/SDG +catecholamine/MS +categoric +categorical/Y +categorization/MS +categorize/RSDGZ +categorized/AU +category/MS +catenate/NF +catenation/MF +cater/GRDZ +catercorner +caterer/M +catering/M +caterpillar/SM +caterwaul/DSG +catfish/MS +catgut/SM +catharses +catharsis/M +cathartic/S +cathedral/SM +catheter/SM +catheterize/GSD +cathode/MS +cathodic +catholic/MS +catholicism +catholicity/MS +cation/MS +cationic +catkin/SM +catlike +catnap/SM +catnapped +catnapping +catnip/MS +catsup's +cattail/SM +catted +cattery/M +cattily +cattiness/SM +catting +cattle/M +cattleman/M +cattlemen +catty/PRST +catwalk/MS +caucus/SDMG +caudal/Y +caught/U +cauldron/MS +cauliflower/MS +caulk/JSGZRD +caulker/M +causal/YS +causality/SM +causate/XVN +causation/M +causative/SY +cause/DSRGMZ +caused/U +causeless +causer/M +causerie/MS +causeway/SGDM +caustic/YS +caustically +causticity/MS +cauterization/SM +cauterize/GSD +cauterized/U +caution/GJDRMSZ +cautionary +cautioner/M +cautious/PIY +cautiousness's/I +cautiousness/SM +cavalcade/MS +cavalier/SGYDP +cavalierness/M +cavalry/MS +cavalryman/M +cavalrymen +cave's +cave/GFRSD +caveat/SM +caveatted +caveatting +caveman/M +cavemen +caver/M +cavern/GSDM +cavernous/Y +caviar/MS +cavil/SJRDGZ +caviler/M +caving/MS +cavity/MFS +cavort/SDG +caw/SMDG +cay's +cay/SC +cayenne/SM +cayman/SM +cayuse/SM +cc +cease/DSCG +ceasefire/S +ceaseless/YP +ceaselessness/SM +ceasing/U +ceca +cecal +cecum/M +cedar/SM +cede/FRSDG +ceded/A +ceder's/F +ceder/SM +cedes/A +cedilla/SM +ceding/A +ceilidh/M +ceiling/MDS +celandine/MS +celebrant/MS +celebrate/XSDGN +celebrated/P +celebratedness/M +celebration/M +celebrator/MS +celebratory +celebrity/MS +celerity/SM +celery/SM +celesta/SM +celestial/YS +celibacy/MS +celibate/SM +cell/GMDS +cellar/RDMGS +cellarer/M +cellist/SM +cello/MS +cellophane/SM +cellphone/S +cellular/SY +cellulite/S +celluloid/SM +cellulose/SM +cement/ZGMRDS +cementa +cementer/M +cementum/SM +cemetery/MS +cenobite/MS +cenobitic +cenotaph/M +cenotaphs +censer/MS +censor/GDMS +censored/U +censorial +censorious/YP +censoriousness/MS +censorship/MS +censure/BRSDZMG +censurer/M +census/SDMG +cent/SZMR +centaur/SM +centavo/SM +centenarian/MS +centenary/S +centennial/YS +center's +center/AC +centerboard/SM +centered +centerer/S +centerfold/S +centering/SM +centerline/SM +centerpiece/SM +centigrade/S +centigram/SM +centiliter/MS +centime/SM +centimeter/SM +centipede/MS +central/STRY +centralism/M +centralist/M +centrality/MS +centralization/CAMS +centralize/CGSD +centralizer/SM +centralizes/A +centrefold's +centric/F +centrifugal/SY +centrifugate/NM +centrifugation/M +centrifuge/GMSD +centripetal/Y +centrist/MS +centroid/MS +centurion/MS +century/MS +cephalic/S +ceramic/MS +ceramicist/S +ceramist/MS +cerate/MD +cereal/MS +cerebellar +cerebellum/MS +cerebra +cerebral/SY +cerebrate/XSDGN +cerebration/M +cerebrum/MS +cerement/SM +ceremonial/YSP +ceremonious/YUP +ceremoniousness's/U +ceremoniousness/MS +ceremony/MS +cerise/SM +cerium/MS +cermet/SM +cert/FS +certain/UY +certainer +certainest +certainty/UMS +certifiable +certifiably +certificate/SDGM +certification/AMC +certified/U +certifier/M +certify/DRSZGNX +certiorari/M +certitude/ISM +cerulean/MS +cervical +cervices/M +cervix/M +cesarean/S +cesium/MS +cessation/SM +cession/FAMSK +cesspit/M +cesspool/SM +cetacean/S +cetera/S +cf +cg +ch/VT +chafe/GDSR +chafer/M +chaff/GRDMS +chaffer/DRG +chafferer/M +chaffinch/SM +chagrin/DGMS +chain's +chain/SGUD +chainlike +chainsaw/SGD +chair/SGDM +chairlady/M +chairlift/MS +chairman/MDGS +chairmanship/MS +chairmen +chairperson/MS +chairwoman/M +chairwomen +chaise/SM +chalcedony/MS +chalet/SM +chalice/DSM +chalk/DSMG +chalkboard/SM +chalkiness/S +chalkline +chalky/RPT +challenge/ZGSRD +challenged/U +challenger/M +challenging/Y +challis/SM +chamber/SZGDRM +chamberer/M +chamberlain/MS +chambermaid/MS +chamberpot/S +chambray/MS +chameleon/SM +chamfer/DMGS +chammy's +chamois/DSMG +chamomile/MS +champ/DGSZ +champagne/MS +champaign/M +champion/MDGS +championship/MS +chance/GMRSD +chanced/M +chancel/SM +chancellery/SM +chancellor/SM +chancellorship/SM +chancery/SM +chanciness/S +chancing/M +chancre/SM +chancy/RPT +chandelier/SM +chandler/MS +change/GZRSD +changeabilities +changeability/UM +changeable/U +changeableness/SM +changeably/U +changed/U +changeless +changeling/M +changeover/SM +changer/M +changing/U +channel/MDRZSG +channeler/M +channeling/M +channelization/SM +channelize/GDS +channellings +chanson/SM +chant/SJGZMRD +chanter/M +chanteuse/MS +chantey/SM +chanticleer/SM +chantry/MS +chanty's +chaos/SM +chaotic +chaotically +chap/MS +chaparral/MS +chapbook/SM +chapeau/MS +chapel/MS +chaperon/GMDS +chaperonage/MS +chaperone's +chaperoned/U +chaplain/MS +chaplaincy/MS +chaplet/SM +chapped +chapping +chapter/SGDM +char/GS +charabanc/MS +character/MDSG +characterful +characteristic/SM +characteristically/U +characterizable/MS +characterization/MS +characterize/DRSBZG +characterized/U +characterizer/M +characterless +charade/SM +charbroil/SDG +charcoal/MGSD +chard/SM +chardonnay/S +charge/EGRSDA +chargeable/P +chargeableness/M +charged/U +charger/AME +chargers +charily +chariness/MS +chariot/SMDG +charioteer/GSDM +charisma/M +charismata +charismatic/S +charismatically +charitable/UP +charitableness/UM +charitablenesses +charitably/U +charity/MS +charlady/M +charlatan/SM +charlatanism/MS +charlatanry/SM +charm/SGMZRD +charmer/M +charming/RYT +charmless +charred +charring +chart/SJMRDGBZ +charted/U +charter's +charter/AGDS +chartered/U +charterer/SM +chartist/SM +chartreuse/MS +chartroom/S +charwoman/M +charwomen +chary/PTR +chase/DSRGZ +chaser/M +chasing/M +chasm/SM +chassis/M +chaste/UTR +chastely +chasten/GSD +chasteness/SM +chastise/ZGLDRS +chastisement/SM +chastiser/M +chastity's/U +chastity/SM +chasuble/SM +chat/MS +chateaus +chatted +chattel/MS +chatter/SZGDRY +chatterbox/MS +chatterer/M +chattily +chattiness/SM +chatting +chatty/RTP +chauffeur/GSMD +chauvinism/MS +chauvinist/MS +chauvinistic +chauvinistically +chaw +cheap/YRNTXSP +cheapen/DG +cheapish +cheapness/MS +cheapskate/MS +cheat/RDSGZ +cheater/M +check's/A +check/GZBSRDM +checkable/U +checkbook/MS +checked/UA +checker/DMG +checkerboard/MS +checklist/S +checkmate/MSDG +checkoff/SM +checkout/S +checkpoint/MS +checkroom/MS +checks/A +checksum/SM +checksummed +checksumming +checkup/MS +cheddar/S +cheek/DMGS +cheekbone/SM +cheekily +cheekiness/SM +cheeky/PRT +cheep/GMDS +cheer/YRDGZS +cheerer/M +cheerful/YP +cheerfuller +cheerfullest +cheerfulness/MS +cheerily +cheeriness/SM +cheerio/S +cheerleader/SM +cheerless/PY +cheerlessness/SM +cheers/S +cheery/PTR +cheese/SDGM +cheeseburger/SM +cheesecake/SM +cheesecloth/M +cheesecloths +cheeseparing/S +cheesiness/SM +cheesy/PRT +cheetah/M +cheetahs +chef/SM +cheffed +cheffing +chelate/XDMNG +chelation/M +chem +chemic +chemical/SYM +chemiluminescence/M +chemiluminescent +chemise/SM +chemist/SM +chemistry/SM +chemotherapeutic/S +chemotherapy/SM +chemurgy/SM +chenille/SM +cherish/GDRS +cherisher/M +cheroot/MS +cherry/SM +chert/MS +cherub/SM +cherubic +cherubim/S +chervil/MS +chess/SM +chessboard/SM +chessman/M +chessmen +chest/MRDS +chesterfield/MS +chestful/S +chestnut/SM +chesty/TR +chevalier/SM +cheviot/S +chevron/DMS +chew/GZSDR +chewer/M +chewiness/S +chewy/RTP +chg +chge +chi/MS +chianti/M +chiaroscuro/SM +chic/SYRPT +chicane/MGDS +chicanery/MS +chichi/RTS +chick/XSNM +chickadee/SM +chicken/GDM +chickenfeed +chickenhearted +chickenpox/MS +chickpea/MS +chickweed/MS +chicle/MS +chicness/S +chicory/MS +chide/GDS +chiding/Y +chief/YRMST +chiefdom/MS +chieftain/SM +chiffon/MS +chiffonier/MS +chigger/MS +chignon/MS +chihuahua/S +chilblain/MS +child/GMYD +childbearing/MS +childbirth/M +childbirths +childcare/S +childes +childhood/MS +childish/YP +childishness/SM +childless/P +childlessness/SM +childlike/P +childlikeness/M +childminders +childproof/GSD +childrearing +children/M +chile's +chili/M +chilies +chill/MRDJGTZPS +chiller/M +chilli's +chilliness/MS +chilling/Y +chillness/MS +chilly/TPRS +chimaera's +chimaerical +chime/DSRGMZ +chimer/M +chimera/SM +chimeric +chimerical +chimney/SMD +chimp/MS +chimpanzee/SM +chin/SGDM +china/MS +chinchilla/SM +chine/MS +chink/DMSG +chinless +chinned +chinner/S +chinning +chino/MS +chinstrap/S +chintz/SM +chintzy/TR +chip/SM +chipboard/M +chipmunk/SM +chipped +chipper/DGS +chipping/MS +chiral +chirography/SM +chiropodist/SM +chiropody/MS +chiropractic/MS +chiropractor/SM +chirp/GDS +chirpy/RT +chirrup/DGS +chisel/ZGSJMDR +chiseler/M +chit/SM +chitchat/SM +chitchatted +chitchatting +chitin/SM +chitinous +chitterlings +chivalric +chivalrous/YP +chivalrously/U +chivalrousness/MS +chivalry/SM +chive/GMDS +chivvy/D +chivying +chlamydia/S +chlamydiae +chloral/MS +chlorate/M +chlordane/MS +chloride/MS +chlorinate/XDSGN +chlorinated/C +chlorinates/C +chlorination/M +chlorine/MS +chlorofluorocarbon/S +chloroform/DMSG +chlorophyll/SM +chloroplast/MS +chloroquine/M +chm +chock/SGRDM +chockablock +chocoholic/S +chocolate/MS +chocolaty +choice/RSMTYP +choiceness/M +choir/SDMG +choirboy/MS +choirmaster/SM +choke/DSRGZ +chokeberry/M +chokecherry/SM +choker/M +chokes/M +choking/Y +choler/SM +cholera/SM +choleric +cholesterol/SM +choline/M +cholinesterase/M +chomp/DSG +choose/GZRS +chooser/M +choosiness/S +choosy/RPT +chop/S +chophouse/SM +chopped +chopper/SDMG +choppily +choppiness/MS +chopping +choppy/RPT +chopstick/SM +choral/SY +chorale/MS +chord/SGMD +chordal +chordata +chordate/MS +chording/M +chore/DSGNM +chorea/MS +choreograph/ZGDR +choreographer/M +choreographic +choreographically +choreographs +choreography/MS +chorines +chorion/M +chorister/SM +choroid/S +chortle/ZGDRS +chortler/M +chorus/GDSM +chose/S +chosen/U +chow/DGMS +chowder/SGDM +chrism/SM +chrissake +christen/SAGD +christened/U +christening/SM +chroma/M +chromate/M +chromatic/PS +chromatically +chromaticism/M +chromaticness/M +chromatics/M +chromatin/MS +chromatogram/MS +chromatograph +chromatographic +chromatography/M +chrome/GMSD +chromic +chromite/M +chromium/SM +chromosomal +chromosome/MS +chromosphere/M +chronic/S +chronically +chronicle/SRDMZG +chronicled/U +chronicler/M +chronograph/M +chronographs +chronography +chronological/Y +chronologist/MS +chronology/MS +chronometer/MS +chronometric +chrysalids +chrysalis/SM +chrysanthemum/MS +chub/MS +chubbiness/SM +chubby/RTP +chuck/GSDM +chuckhole/SM +chuckle/DSG +chuckling/Y +chuff/DM +chug/MS +chugged +chugging +chukka/S +chum/MS +chummed +chummily +chumminess/MS +chumming +chummy/SRTP +chump/MDGS +chumping/M +chunk/SGDM +chunkiness/MS +chunky/RPT +chuntering +church/MDSYG +churchgoer/SM +churchgoing/SM +churchliness/M +churchly/P +churchman/M +churchmen +churchwarden/SM +churchwoman/M +churchwomen +churchyard/SM +churl/SM +churlish/YP +churlishness/SM +churn/SGZRDM +churner/M +churning/M +chute/DSGM +chutney/MS +chutzpa/SM +chutzpah/M +chutzpahs +chyme/SM +château/M +châteaux +châtelaine/SM +ciao/S +cicada/MS +cicatrice/S +cicatrix's +cicerone/MS +ciceroni +cider's/C +cider/SM +cigar/SM +cigarette/MS +cigarillo/MS +cilantro/S +cilia/M +ciliate/FDS +ciliately +cilium/M +cinch/MSDG +cinchona/SM +cincture/MGSD +cinder/DMGS +cine/M +cinema/SM +cinematic +cinematographer/MS +cinematographic +cinematography/MS +cinnabar/MS +cinnamon/MS +cipher/MSGD +ciphered/C +ciphers/C +cir +circa +circadian +circle/RSDGM +circler/M +circlet/MS +circuit/GSMD +circuital +circuitous/YP +circuitousness/MS +circuitry/SM +circuity/MS +circulant +circular/PSMY +circularity/SM +circularize/GSD +circularness/M +circulate/ASDNG +circulation/MA +circulations +circulative +circulatory +circumcise/DRSXNG +circumcised/U +circumciser/M +circumcision/M +circumference/SM +circumferential/Y +circumflex/MSDG +circumlocution/MS +circumlocutory +circumnavigate/DSNGX +circumnavigation/M +circumnavigational +circumpolar +circumscribe/GSD +circumscription/SM +circumspect/Y +circumspection/SM +circumsphere +circumstance/SDMG +circumstantial/YS +circumvent/SBGD +circumvention/MS +circus/SM +cirque/SM +cirrhoses +cirrhosis/M +cirrhotic/S +cirri/M +cirrus/M +cistern/SM +cit/DSG +citadel/SM +citation/SMA +citations/I +cite/ISDAG +citified +citizen/SYM +citizenry/SM +citizenship/MS +citrate/DM +citric +citron/MS +citronella/MS +citrus/SM +city/DSM +cityscape/MS +citywide +civet/SM +civic/S +civics/M +civil/UY +civilian/SM +civility/IMS +civilization/AMS +civilizational/MS +civilize/DRSZG +civilized/PU +civilizedness/M +civilizer/M +civilizes/AU +civvies +ck/C +cl/GJ +clack/SDG +clad/U +cladding/SM +clads +claim/CDRSKAEGZ +claimable +claimant/MS +claimed/U +claimer/KMACE +clairvoyance/MS +clairvoyant/YS +clam/MS +clambake/MS +clamber/SDRZG +clamberer/M +clammed +clammily +clamminess/MS +clamming +clammy/TPR +clamor/GDRMSZ +clamorer/M +clamorous/PUY +clamorousness/UM +clamp/MRDGS +clampdown/SM +clamper/M +clamshell/MS +clan/MS +clandestine/YP +clandestineness/M +clang/SGZRD +clanger/M +clangor/MDSG +clangorous/Y +clank/SGDM +clanking/Y +clannish/PY +clannishness/SM +clansman/M +clansmen +clap/S +clapboard/SDGM +clapped +clapper/GMDS +clapping +claptrap/SM +claque/MS +claret/MDGS +clarification/M +clarifier/M +clarify/NGXDRS +clarinet/SM +clarinetist/SM +clarinettist's +clarion/GSMD +clarities +clarity/UM +clash/RSDG +clasher/M +clasp's +clasp/UGSD +clasped/M +clasper/M +class/GRSDM +classer/M +classic/S +classical/Y +classicism/SM +classicist/SM +classics/M +classifiable/U +classification/AMC +classificatory +classified/S +classifier/SM +classify/CNXASDG +classiness/SM +classless/P +classmate/MS +classroom/MS +classwork/M +classy/PRT +clatter/SGDR +clatterer/M +clattering/Y +clattery +clausal +clause/MS +claustrophobia/SM +claustrophobic +clave's/F +clave/RM +clavichord/SM +clavicle/MS +clavier/MS +claw/GDRMS +clawer/M +clay/MDGS +clayey +clayier +clayiest +claymore/MS +clean/UYRDPT +cleanable +cleaner/MS +cleaning/SM +cleanliness/UMS +cleanly/PRTU +cleanness/MSU +cleans/GDRSZ +cleanse +cleanser/M +cleanup/MS +clear/UTRD +clearance/MS +clearcut +clearer/M +clearheaded/PY +clearheadedness/M +clearing/MS +clearinghouse/S +clearly +clearness/MS +clears +clearway/M +cleat/MDSG +cleavage/MS +cleave/RSDGZ +cleaver/M +clef/SM +cleft/MDGS +clematis/MS +clemence +clemency/ISM +clement/IY +clements +clench/UD +clenches +clenching +clerestory/MS +clergy/MS +clergyman/M +clergymen +clergywoman +clergywomen +cleric/SM +clerical/YS +clericalism/SM +clerk/SGYDM +clerkship/MS +clever/RYPT +cleverness/SM +clevis/SM +clew/DMGS +cliché/SM +clichéd +click/GZSRDM +clicker/M +client/SM +clientèle/SM +cliff/SM +cliffhanger/MS +cliffhanging +climacteric/SM +climactic +climate/MS +climatic +climatically +climatological/Y +climatologist/SM +climatology/MS +climax/MDSG +climb/BGZSJRD +climbable/U +climbdown +climbed/U +climber/M +clime/SM +clinch/DRSZG +clincher/M +clinching/Y +cling/U +clinger/MS +clinging +clingy/TR +clinic/MS +clinical/Y +clinician/MS +clink/RDGSZ +clinker/GMD +clinometer/MIS +cliometric/S +cliometrician/S +clip/SM +clipboard/SM +clipped/U +clipper/MS +clipping/SM +clique/SDGM +cliquey +cliquier +cliquiest +cliquish/YP +cliquishness/SM +clitoral +clitorides +clitoris/MS +cloaca/M +cloacae +cloak's +cloak/USDG +cloakroom/MS +clobber/DGS +cloche/MS +clock/SGZRDMJ +clocker/M +clockmaker/M +clockwatcher +clockwise +clockwork/MS +clod/MS +clodded +clodding +cloddish/P +cloddishness/M +clodhopper/SM +clog's +clog/US +clogged/U +clogging/U +cloisonnes +cloisonné +cloister/MDGS +cloistral +clomp/MDSG +clonal +clone/DSRGMZ +clonk/SGD +clop/S +clopped +clopping +close/EDSRG +closed/U +closefisted +closely +closemouthed +closeness/MS +closeout/MS +closer/EM +closers +closest +closet/MDSG +closeup/S +closing/S +closure's/I +closure/EMS +closured +closuring +clot/MS +cloth/GJMSD +clothbound +clothe/UDSG +clothesbrush +clotheshorse/MS +clothesline/SDGM +clothesman +clothesmen +clothespin/MS +clothier/MS +clothing/M +cloths +clotted +clotting +cloture/MDSG +cloud/SGMD +cloudburst/MS +clouded/U +cloudiness/SM +cloudless/YP +cloudlessness/M +cloudscape/SM +cloudy/TPR +clout/GSMD +clove/SRMZ +cloven +clover/M +cloverleaf/MS +clown/DMSG +clownish/PY +clownishness/SM +cloy/DSG +cloying/Y +club/MS +clubbed/M +clubbing/M +clubfeet +clubfoot/DM +clubhouse/SM +clubroom/SM +cluck/GSDM +clue/MGDS +clueless +clump/MDGS +clumpy/RT +clumsily +clumsiness/MS +clumsy/PRT +clung +clunk/SGZRDM +clunky/PRYT +cluster/SGJMD +clustered/AU +clusters/A +clutch/DSG +clutter/GSD +cluttered/U +cm +cnidarian/MS +co/DES +coach/MSRDG +coacher/M +coachman/M +coachmen +coachwork/M +coadjutor/MS +coagulable +coagulant/SM +coagulate/GNXSD +coagulation/M +coagulator/S +coal/MDRGS +coaler/M +coalesce/GDS +coalescence/SM +coalescent +coalface/SM +coalfield/MS +coalition/MS +coalitionist/SM +coalminers +coarse/TYRP +coarsen/SGD +coarseness/SM +coast/SMRDGZ +coastal +coaster/M +coastguard/MS +coastline/SM +coat/MDRGZJS +coated/U +coating/M +coattail/S +coattest +coauthor/MDGS +coax/GZDSR +coaxer/M +coaxial/Y +coaxing/Y +cob/SM +cobalt/MS +cobbed +cobbing +cobble/SRDGMZ +cobbler/M +cobblestone/MSD +coble/M +cobra/MS +cobweb/SM +cobwebbed +cobwebbing +cobwebby/RT +coca/MS +cocaine/MS +cocci/MS +coccus/M +coccyges +coccyx/M +cochineal/SM +cochlea/SM +cochleae +cochlear +cock/GDRMS +cockade/SM +cockamamie +cockatoo/SM +cockatrice/MS +cockcrow/MS +cocker/M +cockerel/MS +cockeye/DM +cockeyed/PY +cockfight/MJSG +cockfighting/M +cockily +cockiness/MS +cockle/SDGM +cocklebur/M +cockleshell/SM +cockney/MS +cockpit/MS +cockroach/SM +cockscomb/SM +cockshies +cocksucker/S! +cocksure +cocktail/GDMS +cocky/RPT +coco/MS +cocoa/SM +coconut/SM +cocoon/GDMS +cod/MDRSZGJ +coda/SM +codded +codding +coddle/GSRD +coddler/M +code's +code/SCZGJRD +codebook/S +codebreak/R +coded/UA +codeine/MS +codename/D +codependency/S +codependent/S +coder/CM +codes/A +codetermine/S +codeword/SM +codex/M +codfish/SM +codger/MS +codices/M +codicil/SM +codification/M +codifier/M +codify/NZXGRSD +coding/M +codling/M +codpiece/MS +coed/SM +coedited +coediting +coeditor/MS +coedits +coeducation/SM +coeducational +coefficient/SYM +coelenterate/MS +coequal/SY +coerce/SRDXVGNZ +coercer/M +coercible/I +coercion/M +coercive/PY +coerciveness/M +coeval/YS +coexist/GDS +coexistence/MS +coexistent +coextensive/Y +cofactor/MS +coffee/SM +coffeecake/SM +coffeecup +coffeehouse/SM +coffeemaker/S +coffeepot/MS +coffer/DMSG +cofferdam/SM +coffin/DMGS +cog/MS +cogency/MS +cogent/Y +cogged +cogging +cogitate/DSXNGV +cogitation/M +cogitator/MS +cognac/SM +cognate/SXYN +cognation/M +cognition/SAM +cognitional +cognitive/SY +cognizable +cognizance/MAI +cognizances/A +cognizant/I +cognomen/SM +cognoscente +cognoscenti +cogwheel/SM +cohabit/SDG +cohabitant/MS +cohabitation/SM +cohabitational +coheir/MS +cohere/GSRD +coherence/SIM +coherencies +coherency/I +coherent/IY +coherer/M +cohesion/MS +cohesive/PY +cohesiveness/SM +coho/MS +cohoes +cohort/SM +coif/SM +coiffed +coiffing +coiffure/MGSD +coil/UGSAD +coin/GZSDRM +coinage's/A +coinage/SM +coincide/GSD +coincidence/MS +coincident/Y +coincidental/Y +coined/U +coiner/M +coinsurance/SM +cointreau +coital/Y +coitus/SM +coke/MGDS +col/SD +cola/SM +colander/SM +colatitude/MS +cold/YRPST +coldblooded +coldish +coldness/MS +coleslaw/SM +coleus/SM +colic/SM +colicky +coliform +coliseum/SM +colitis/MS +coll/G +collaborate/VGNXSD +collaboration/M +collaborative/SY +collaborator/SM +collage/MGSD +collagen/M +collapse/SDG +collapsibility/M +collapsible +collar/DMGS +collarbone/MS +collard/SM +collarless +collate/SDVNGX +collated/U +collateral/SYM +collation/M +collator/MS +colleague/SDGM +collect/SAGD +collected/PY +collectedness/M +collectible/S +collection/AMS +collective/SY +collectivism/SM +collectivist/MS +collectivity/MS +collectivization/MS +collectivize/DSG +collector/MS +colleen/SM +college/SM +collegiality/S +collegian/SM +collegiate/Y +collide/SDG +collie/MZSRD +collier/M +colliery/MS +collimate/C +collimated/U +collimates +collimating +collimation/M +collimator/M +collinear +collinearity/M +collision/SM +collisional +collocate/XSDGN +collocation/M +colloid/MS +colloidal/Y +colloq +colloquial/SY +colloquialism/MS +colloquies +colloquium/SM +colloquy/M +collude/SDG +collusion/SM +collusive +collying +cologne/MSD +colon/SM +colonel/MS +colonelcy/MS +colonial/SPY +colonialism/MS +colonialist/MS +colonist/SM +colonization/ACSM +colonize/ACSDG +colonized/U +colonizer/MS +colonizes/U +colonnade/MSD +colony/SM +colophon/SM +color/SRDMGZJ +colorant/SM +coloration/EMS +coloratura/SM +colorblind/P +colorblindness/S +colored/USE +colorer/M +colorfast/P +colorfastness/SM +colorful/PY +colorfulness/MS +colorimeter/SM +colorimetry +coloring/M +colorization/S +colorize/GSD +colorizing/C +colorless/PY +colorlessness/SM +colors/EA +colossal/Y +colossi +colossus/M +colostomy/SM +colostrum/SM +colt/MRS +colter/M +coltish/PY +coltishness/M +columbine/SM +column/SDM +columnar +columnist/MS +columnize/GSD +com/LJRTZG +coma/SM +comae +comaker/SM +comatose +comb/SGZDRMJ +combat/SVGMD +combatant/SM +combative/PY +combativeness/MS +combed/U +comber/M +combination/ASM +combinational/A +combinator/SM +combinatorial/Y +combinatoric/S +combine/ZGBRSD +combined/AU +combiner/M +combines/A +combining/A +combo/MS +combusted +combustibility/SM +combustible/SI +combustion/MS +combustive +come/IZSRGJ +comeback/SM +comedian/SM +comedic +comedienne/SM +comedown/MS +comedy/SM +comeliness/SM +comely/TPR +comer/IM +comes/M +comestible/MS +comet/SM +cometary +cometh +comeuppance/SM +comfit's +comfit/SE +comfort/ESMDG +comfortability/S +comfortable/U +comfortableness/MS +comfortably/U +comforted/U +comforter/MS +comforting/YE +comfy/RT +comic/MS +comical/Y +comicality/MS +comity/SM +comm +comma/MS +command/SZRDMGL +commandant/MS +commandeer/SDG +commander/M +commanding/Y +commandment/SM +commando/SM +commemorate/SDVNGX +commemoration/M +commemorative/YS +commemorator/S +commence/ALDSG +commencement/AMS +commencer/M +commend/GSADRB +commendably +commendation/ASM +commendatory/A +commender/AM +commensurable/I +commensurate/IY +commensurates +commensuration/SM +comment's +comment/SUGD +commentary/MS +commentate/GSD +commentator/SM +commenter/M +commerce/MGSD +commercial/PYS +commercialism/MS +commercialization/SM +commercialize/GSD +commie/SM +commingle/GSD +commiserate/VGNXSD +commiseration/M +commissar/MS +commissariat/MS +commissary/MS +commission's/A +commission/ASCGD +commissioner/SM +commit/SA +commitment/SM +committable +committal/MA +committals +committed/UA +committee/MS +committeeman/M +committeemen +committeewoman/M +committeewomen +committing/A +commode/MS +commodes/IE +commodious/YIP +commodiousness/MI +commodity/MS +commodore/SM +common/RYUPT +commonality/MS +commonalty/MS +commoner/MS +commonness/MSU +commonplace/SP +commonplaceness/M +commons/M +commonsense +commonweal/SHM +commonwealth/M +commonwealths +commotion/MS +communal/Y +communality/M +commune/XSDNG +communicability/MS +communicable/IU +communicably +communicant/MS +communicate/VNGXSD +communication/M +communicational +communicative/PY +communicativeness/M +communicator/SM +communion/M +communique/S +communism/MS +communist/MS +communistic +communitarian/M +community/MS +communize/SDG +commutable/I +commutate/XVGNSD +commutation/M +commutative/Y +commutativity +commutator/MS +commute/BZGRSD +commuter/M +comp/GSYD +compact/TZGSPRDY +compaction/M +compactness/MS +compactor/MS +companion/GBSMD +companionable/P +companionableness/M +companionably +companionship/MS +companionway/MS +company/MSDG +comparabilities +comparability/IM +comparable/P +comparableness/M +comparably/I +comparative/PYS +comparativeness/M +comparator/SM +compare/GRSDB +comparer/M +comparison/MS +compartment/SDMG +compartmental +compartmentalization/SM +compartmentalize/DSG +compass/MSDG +compassion/MS +compassionate/PSDGY +compassionateness/M +compatibility/IMS +compatible/SI +compatibleness/M +compatibly/I +compatriot/SM +compeer/DSGM +compel/S +compellable +compelled +compelling/YM +compendious +compendium/MS +compensable +compensate/XVNGSD +compensated/U +compensation/M +compensator/M +compensatory +compete/GSD +competence/ISM +competency's +competency/IS +competent/IY +competition/SM +competitive/YP +competitiveness/SM +competitor/MS +compilable/U +compilation/SAM +compile/ASDCG +compiler's +compiler/CS +complacence/S +complacency/SM +complacent/Y +complain/GZRDS +complainant/MS +complainer/M +complaining/YU +complaint/MS +complaisance/SM +complaisant/Y +complected +complement/ZSMRDG +complementariness/M +complementarity +complementary/SP +complementation/M +complementer/M +complete/BTYVNGPRSDX +completed/U +completely/I +completeness/ISM +completer/M +completion/MI +complex/TGPRSDY +complexion/DMS +complexional +complexity/MS +complexness/M +compliance/SM +compliant/Y +complicate/SDG +complicated/YP +complicatedness/M +complication/M +complicator/SM +complicit +complicity/MS +complier/M +compliment/ZSMRDG +complimentary/U +complimenter/M +comply/ZXRSDNG +component/SM +comport/GLSD +comportment/SM +compose/CGASDE +composed/PY +composedness/M +composer/CM +composers +composite/YSDXNG +composition/CMA +compositional/Y +compositions/C +compositor/MS +compost/DMGS +composure/ESM +compote/MS +compound/RDMBGS +compounded/U +compounder/M +comprehend/DGS +comprehending/U +comprehensibility/SIM +comprehensible/PI +comprehensibleness/IM +comprehensibly/I +comprehension/IMS +comprehensive/YPS +comprehensiveness/SM +compress/SDUGC +compressed/Y +compressibility/IM +compressible/I +compression/CSM +compressional +compressive/Y +compressor/MS +comprise/GSD +compromise/SRDGMZ +compromiser/M +compromising/UY +comptroller/SM +compulsion/SM +compulsive/PYS +compulsiveness/MS +compulsivity +compulsorily +compulsory/S +compunction/MS +computability/M +computable/UI +computably +computation/SM +computational/Y +compute/RSDZBG +computed/A +computer/M +computerese +computerization/MS +computerize/SDG +computes/A +computing/A +comrade/YMS +comradely/P +comradeship/MS +con/SGM +concatenate/XSDG +concave/YP +concaveness/MS +conceal/BSZGRDL +concealed/U +concealer/M +concealing/Y +concealment/MS +conceded/Y +conceit/SGDM +conceited/YP +conceitedness/SM +conceivable/IU +conceivably/I +conceive/BGRSD +conceiver/M +concentrate/VNGSDX +concentration/M +concentrator/MS +concentrically +concept/SVM +conception/MS +conceptional +conceptual/Y +conceptuality/M +conceptualization's +conceptualization/A +conceptualizations +conceptualize/DRSG +conceptualizing/A +concern/USGD +concerned/YU +concert's +concert/EDSG +concerted/PY +concertina/MDGS +concertize/GDS +concertmaster/MS +concerto/SM +concession/R +concessionaire/SM +concessional +concessionary +conch/MDG +conchs +concierge/SM +conciliar +conciliate/GNVX +conciliation/ASM +conciliator/MS +conciliatory/A +concise/TYRNPX +conciseness/SM +concision/M +conclave/S +conclude/RSDG +concluder/M +conclusion/SM +conclusive/IPY +conclusiveness/ISM +concoct/RDVGS +concocter/M +concoction/SM +concomitant/YS +concordance/MS +concordant/Y +concordat/SM +concourse +concrete/NGXRSDPYM +concreteness/MS +concretion/M +concubinage/SM +concubine/SM +concupiscence/SM +concupiscent +concur/S +concurrence/MS +concuss/VD +concussion/MS +condemn/ZSGRDB +condemnate/XN +condemnation/M +condemnatory +condemner/M +condensate/NMXS +condensation/M +condense/ZGSD +condenser/M +condensible +condescend +condescending/Y +condescension/MS +condign +condiment/SM +condition's +condition/AGSJD +conditional/UY +conditionals +conditioned/U +conditioner/MS +conditioning/M +condo/SM +condole +condolence/MS +condom/SM +condominium/MS +condone/GRSD +condoner/M +condor/MS +conduce/VGSD +conducive/P +conduciveness/M +conduct/V +conductance/SM +conductibility/SM +conductible +conduction/MS +conductive/Y +conductivity/MS +conductor/MS +conductress/MS +conduit/MS +coneflower/M +coney's +confab/MS +confabbed +confabbing +confabulate/XSDGN +confabulation/M +confect/S +confection/RDMGZS +confectioner/M +confectionery/SM +confectionist +confederacy/MS +confederate/M +confer/SB +conferee/MS +conference/DSGM +conferrable +conferral/SM +conferred +conferrer/SM +conferring +confessed/Y +confession/MS +confessional/SY +confessor/SM +confetti/M +confidant/SM +confidante/SM +confide/ZGRSD +confidence/SM +confident/Y +confidential/PY +confidentiality/MS +confidentialness/M +confider/M +confiding/PY +configuration/ASM +configure/AGSDB +confine/L +confined/U +confinement/MS +confiner/M +confirm/AGDS +confirmation/ASM +confirmatory +confirmed/YP +confirmedness/M +confiscate/DSGNX +confiscation/M +confiscator/MS +confiscatory +conflagration/MS +conflate/NGSDX +conflation/M +conflict/SVGDM +conflicting/Y +confluence/MS +conform/B +conformable/U +conformal +conformance/SM +conformational/Y +conformer/M +conformism/SM +conformist/SM +conformities +conformity/MUI +confound/R +confounded/Y +confront/Z +confrontation/SM +confrontational +confronter/M +confrère/MS +confuse/RBZ +confused/PY +confusedness/M +confusing/Y +confutation/MS +confute/GRSD +confuter/M +conga/MDG +congeal/GSDL +congealment/MS +congenial/U +congeniality/UM +conger/SM +congeries/M +congest/VGSD +congestion/MS +conglomerate/XDSNGVM +conglomeration/M +congrats +congratulate/NGXSD +congratulation/M +congratulatory +congregate/DSXGN +congregation/M +congregational +congregationalism/MS +congregationalist/MS +congress/MSDG +congressional/Y +congressman/M +congressmen +congresspeople +congressperson/S +congresswoman/M +congresswomen +congruence/IM +congruences +congruency/M +congruent/YI +congruential +congruity/MSI +congruous/YIP +congruousness/IM +conic/S +conical/PSY +conicalness/M +conics/M +conifer/MS +coniferous +conjectural/Y +conjecture/GMDRS +conjecturer/M +conjoint +conjugacy +conjugal/Y +conjugate/XVNGYSDP +conjugation/M +conjunct/DSV +conjunctiva/MS +conjunctive/YS +conjunctivitis/SM +conjuration/MS +conjure/RSDZG +conjurer/M +conjuring/M +conk/ZDR +conker/M +conman +conn/GVDR +connect/ADGES +connected/U +connectedly/E +connectedness/ME +connectible +connection/AME +connectionless +connections/E +connective/SYM +connectivity/MS +connector/MS +connexion/MS +conniption/MS +connivance/MS +connive/ZGRSD +conniver/M +connoisseur/MS +connotative/Y +connubial/Y +conquer/RDSBZG +conquerable/U +conquered/AU +conqueror/MS +conquers/A +conquest/ASM +conquistador/MS +consanguineous/Y +consanguinity/SM +conscienceless +conscientious/YP +conscientiousness/MS +conscionable/U +conscious/UYSP +consciousness/MUS +conscription/SM +consecrate/XDSNGV +consecrated/AU +consecrates/A +consecrating/A +consecration/AMS +consecutive/YP +consecutiveness/M +consensus/SM +consent/SZGRD +consenter/M +consenting/Y +consequence +consequent/PSY +consequential/IY +consequentiality/S +consequentialness/M +consequently/I +conservancy/SM +conservation/SM +conservationism +conservationist/SM +conservatism/SM +conservative/SYP +conservativeness/M +conservator/MS +conservatory/MS +consider/GASD +considerable/I +considerables +considerably/I +considerate/XIPNY +considerateness/MSI +consideration/ASMI +considered/U +considerer/M +considering/S +consign/ASGD +consignee/SM +consignment/SM +consist/DSG +consistence/S +consistency/IMS +consistent/IY +consistory/MS +consolable/I +consolation's/E +consolation/MS +consolatory +console/ZBG +consoled/U +consoler/M +consolidate/NGDSX +consolidated/AU +consolidates/A +consolidation/M +consolidator/SM +consoling/Y +consommé/S +consonance/IM +consonances +consonant/MYS +consonantal +consortia +consortium/M +conspectus/MS +conspicuous/YIP +conspicuousness/IMS +conspiracy/MS +conspirator/SM +conspiratorial/Y +constable +constabulary/MS +constance +constancy/IMS +constant/IY +constants +constellation/SM +consternate/XNGSD +consternation/M +constipate/XDSNG +constipation/M +constituency/MS +constituent/SYM +constitute/NGVXDS +constituted/A +constitutes/A +constituting/A +constitution/AMS +constitutional/SY +constitutionality's +constitutionality/US +constitutionally/U +constitutive/Y +constrain +constrained/U +constrainedly +constraint/MS +constrict/SDGV +constriction/MS +constrictor/MS +construable +construct/ASDGV +constructibility +constructible/A +construction/MAS +constructional/Y +constructionist/MS +constructions/C +constructive/YP +constructiveness/SM +constructor/MS +construe/GSD +consul/KMS +consular/S +consulate/MS +consulship/MS +consult/RDVGS +consultancy/S +consultant/MS +consultation/SM +consultative +consulted/A +consulter/M +consumable/S +consume/JZGSDB +consumed/Y +consumer/M +consumerism/MS +consumerist/S +consuming/Y +consummate/DSGVY +consummated/U +consumption/SM +consumptive/YS +cont +cont'd +contact's/A +contact/BGD +contacted/A +contacts/A +contagion/SM +contagious/YP +contagiousness/MS +contain/SLZGBRD +container/M +containerization/SM +containerize/GSD +containment/SM +contaminant/SM +contaminate/SDCXNG +contaminated/AU +contaminates/A +contaminating/A +contamination/CM +contaminative +contaminator/MS +contd +contemn/SGD +contemplate/DVNGX +contemplation/M +contemplative/PSY +contemplativeness/M +contemporaneity/MS +contemporaneous/PY +contemporaneousness/M +contempt/M +contemptible/P +contemptibleness/M +contemptibly +contemptuous/PY +contemptuousness/SM +content/EMDLSG +contented/YP +contentedly/E +contentedness/SM +contention/MS +contentious/PY +contentiousness/SM +contently +contentment's +contentment/ES +conterminous/Y +contestable/I +contestant/SM +contested/U +contextualize/GDS +contiguity/MS +contiguous/YP +contiguousness/M +continence/ISM +continent's +continent/IY +continental/SY +continents +contingency/SM +contingent/SMY +continua +continuable +continual/Y +continuance/ESM +continuant/M +continuation/ESM +continue/ESDG +continuer/M +continuity/SEM +continuous/YE +continuousness/M +continuum/M +contort/VGD +contortion/MS +contortionist/SM +contour +contra/S +contraband/SM +contrabass/M +contraception/SM +contraceptive/S +contract/DG +contractible +contractile +contractual/Y +contradict/GDS +contradiction/MS +contradictorily +contradictoriness/M +contradictory/PS +contradistinction/MS +contraflow/S +contrail/M +contraindicate/SDVNGX +contraindication/M +contralto/SM +contrapositive/S +contraption/MS +contrapuntal/Y +contrariety/MS +contrarily +contrariness/MS +contrariwise +contrary/PS +contrast/SRDVGZ +contrasting/Y +contrastive/Y +contravene/GSRD +contravener/M +contravention/MS +contretemps/M +contribute/XVNZRD +contribution/M +contributive/Y +contributor/SM +contributorily +contributory/S +contrite/NXP +contriteness/M +contrition/M +contrivance/SM +contrive/ZGRSD +contriver/M +control's +control/CS +controllability/M +controllable/IU +controllably/U +controlled/CU +controller/SM +controlling/C +controversial/UY +controversialists +controversy/MS +controvert/DGS +controvertible/I +contumacious/Y +contumacy/MS +contumelious +contumely/MS +contuse/NGXSD +contusion/M +conundrum/SM +conurbation/MS +convalesce/GDS +convalescence/SM +convalescent/S +convect/DSVG +convection/MS +convectional +convector +convene/ASDG +convener/MS +convenience/ISM +convenient/IY +conventicle/SM +convention/MA +conventional/UY +conventionalism/M +conventionalist/M +conventionality/SUM +conventionalize/GDS +conventions +convergence/MS +convergent +conversant/Y +conversation/SM +conversational/Y +conversationalist/SM +conversazione/M +converse/Y +conversion/AM +conversioning +convert/GADS +converted/U +converter/MS +convertibility's/I +convertibility/SM +convertible/PS +convertibleness/M +convex/Y +convexity/MS +convey/BDGS +conveyance/DRSGMZ +conveyancer/M +conveyancing/M +conveyor/MS +convict/SVGD +conviction/MS +convince/RSDZG +convinced/U +convincer/M +convincing/PUY +convincingness/M +convivial/Y +conviviality/MS +convoke/GSD +convolute/XDNY +convolution/M +convolve/C +convolved +convolves +convolving +convoy/GMDS +convulse/SDXVNG +convulsion/M +convulsive/YP +convulsiveness/M +cony/SM +coo/GSD +cook/GZDRMJS +cookbook/SM +cooked/AU +cooker/M +cookery/MS +cookie/SM +cooking/M +cookout/SM +cooks/A +cookware/SM +cooky's +cool/YDRPJGZTS +coolant/SM +cooled/U +cooler/M +coolheaded +coolie/MS +coolness/MS +coon/MS! +coonskin/MS +coop/MDRGZS +cooper/GDM +cooperage/MS +cooperate/VNGXSD +cooperation/M +cooperative/PSY +cooperativeness/SM +cooperator/MS +coordinate/XNGVYPDS +coordinated/U +coordinateness/M +coordination/M +coordinator/MS +coot/MS +cootie/SM +cop/SJMDRG +copay/S +cope/S +coper/M +copied/A +copier/M +copies/A +copilot/SM +coping/M +copious/YP +copiousness/SM +coplanar +copolymer/MS +copora +copped +copper/MSGD +copperhead/MS +copperplate/MS +coppersmith/M +coppersmiths +coppery +coppice's +copping +copra/MS +coprolite/M +coprophagous +cops/GDS +copse/M +copter/SM +copula/MS +copulate/XDSNGV +copulation/M +copulative/S +copy/MZBDSRG +copybook/MS +copycat/SM +copycatted +copycatting +copyist/SM +copyright/MSRDGZ +copyrighter/M +copywriter/MS +coquetry/MS +coquette/DSMG +coquettish/Y +coracle/SM +coral/SM +coralline +corbel/GMDJS +cord/FSAEM +cordage/MS +corded/AE +corder/AM +cordial/PYS +cordiality/MS +cordialness/M +cordillera/MS +cording/MA +cordite/MS +cordless +cordon/DMSG +cordovan/SM +corduroy/GDMS +core/MZGDRS +cored/A +corer/M +corespondent/MS +corgi/MS +coriander/SM +coring/M +cork/GZDRMS +corked/U +corker/M +corks/U +corkscrew/DMGS +corm/MS +cormorant/MS +corn/GZDRMS +cornball/SM +cornbread/S +corncob/SM +corncrake/M +cornea/SM +corneal +corner/GDM +cornerstone/MS +cornet/SM +cornfield/SM +cornflake/S +cornflour/M +cornflower/SM +cornice/GSDM +cornily +corniness/S +cornmeal/S +cornrow/GDS +cornstalk/MS +cornstarch/SM +cornucopia/MS +corny/RPT +corolla/MS +corollary/SM +corona/SM +coronal/MS +coronary/S +coronate/NX +coronation/M +coroner/MS +coronet/DMS +coroutine/SM +corp/S +corpora/MS +corporal/SYM +corporate/INVXS +corporately +corporation/MI +corporatism/M +corporatist +corporeal/IY +corporeality/MS +corporealness/M +corps/SM +corpse/M +corpsman/M +corpsmen +corpulence/MS +corpulent/YP +corpulentness/S +corpus/M +corpuscle/SM +corpuscular +corr +corral/MS +corralled +corralling +correct/BPSDRYTGV +correctable/U +corrected/U +correction/MS +correctional +corrective/YPS +correctly/I +correctness/MSI +corrector/MS +correlate/SDXVNG +correlated/U +correlation/M +correlative/YS +correspond/DSG +correspondence/MS +correspondent/SM +corresponding/Y +corridor/SM +corrigenda +corrigendum/M +corrigible/I +corroborate/GNVXDS +corroborated/U +corroboration/M +corroborative/Y +corroborator/MS +corroboratory +corrode/SDG +corrodible +corrosion/SM +corrosive/YPS +corrosiveness/M +corrugate/NGXSD +corrugation/M +corrupt/DRYPTSGV +corrupted/U +corrupter/M +corruptibility/SMI +corruptible/I +corruption/IM +corruptions +corruptive/Y +corruptness/MS +corsage/MS +corsair/SM +corset/GMDS +cortex/M +cortical/Y +cortices +corticosteroid/SM +cortisone/SM +cortège/MS +corundum/MS +coruscate/XSDGN +coruscation/M +corvette/MS +cos/GDS +cosign/SRDZG +cosignatory/MS +cosily +cosine/MS +cosiness/MS +cosmetic/SM +cosmetically +cosmetician/MS +cosmetologist/MS +cosmetology/MS +cosmic +cosmical/Y +cosmogonist/MS +cosmogony/SM +cosmological/Y +cosmologist/MS +cosmology/SM +cosmonaut/MS +cosmopolitan/SM +cosmopolitanism/MS +cosmos/SM +cosponsor/DSG +cossack/S +cosset/GDS +cost/MYGVJS +costar/S +costarred +costarring +costive/PY +costiveness/M +costless +costliness/SM +costly/RTP +costume/ZMGSRD +costumer/M +cot/SGMD +cotangent/SM +cote/MS +coterie/MS +coterminous/Y +cotillion/SM +cottage/ZMGSRD +cottager/M +cottar's +cotted +cotter/SDM +cotton/GSDM +cottonmouth/M +cottonmouths +cottonseed/MS +cottontail/SM +cottonwood/SM +cottony +cotyledon/MS +couch/MSDG +couching/M +cougar/MS +cough/RDG +cougher/M +coughs +could've +could/T +couldn't +coulomb/SM +coulée/MS +council/SM +councilman/M +councilmen +councilor/MS +councilperson/S +councilwoman/M +councilwomen +counsel/GSDM +counsellings +counselor/MS +count/EGARDS +countability/E +countable/U +countably/U +countdown/SM +counted/U +countenance's +countenance/EGDS +countenancer/M +counter's/E +counter/GSMD +counteract/DSVG +counteraction/SM +counterargument/SM +counterattack/DRMGS +counterbalance/MSDG +counterclaim/GSDM +counterclockwise +counterculture/MS +countercyclical +counterespionage/MS +counterexample/S +counterfeit/ZSGRD +counterfeiter/M +counterflow +counterfoil/MS +counterforce/M +counterinsurgency/MS +counterintelligence/MS +counterintuitive +counterman/M +countermand/DSG +countermeasure/SM +countermen +counteroffensive/SM +counteroffer/SM +counterpane/SM +counterpart/SM +counterpoint/GSDM +counterpoise/GMSD +counterproductive +counterproposal/M +counterrevolution/MS +counterrevolutionary/MS +counters/E +countersign/SDG +countersignature/MS +countersink/SG +counterspy/MS +counterstrike +countersunk +countertenor/SM +countervail/DSG +counterweight/GMDS +countess/MS +countless/Y +countrify/D +country/MS +countryman/M +countrymen +countryside/MS +countrywide +countrywoman/M +countrywomen +county/SM +coup's +coup/ASDG +coupe/MS +couple's +couple/ACU +coupled/CU +coupler's +coupler/C +couplers +couples/CU +couplet/SM +coupling's/C +coupling/SM +coupon/SM +courage/MS +courageous/U +courageously +courageousness/MS +courages/E +courgette/MS +courier/GMDS +course's/AF +course/EGSRDM +courser's/E +courser/SM +courses/FA +coursework +coursing/M +court/GZMYRDS +courteous/PEY +courteousness/EM +courteousnesses +courtesan/MS +courtesied +courtesy/ESM +courtesying +courthouse/MS +courtier/SM +courtliness/MS +courtly/RTP +courtroom/MS +courtship/SM +courtyard/SM +couscous/MS +cousin/YMS +cousinly/U +couture/SM +couturier/SM +covalent/Y +covariance/SM +covariant/S +covariate/SN +covary +cove/DRSMZG +coven/SM +covenant/SGRDM +covenanted/U +covenanter/M +cover/AEGUDS +coverable/E +coverage/MS +coverall/DMS +coverer/AME +covering/MS +coverlet/MS +covers/M +coversheet +covert/YPS +covertness/SM +covet/SGRD +coveter/M +coveting/Y +covetous/PY +covetousness/SM +covey/SM +covington +cow/MDRSZG +coward/MYS +cowardice/MS +cowardliness/MS +cowardly/P +cowbell/MS +cowbird/MS +cowboy/MS +cowcatcher/SM +cowed/Y +cower/RDGZ +cowering/Y +cowgirl/MS +cowhand/S +cowherd/SM +cowhide/MGSD +cowl/SGMD +cowlick/MS +cowling/M +cowman/M +cowmen +coworker/MS +cowpoke/MS +cowpony +cowpox/MS +cowpunch/RZ +cowpuncher/M +cowrie/SM +cowshed/SM +cowslip/MS +cox/MDSG +coxcomb/MS +coxswain/GSMD +coy/CDSG +coyer +coyest +coyly +coyness/MS +coyote/SM +coypu/SM +cozen/SGD +cozenage/MS +cozily +coziness/MS +cozy/DSRTPG +cpd +cpl +cps +crab/MS +crabapple +crabbed/YP +crabbedness/M +crabber/MS +crabbily +crabbiness/S +crabbing/M +crabby/PRT +crabgrass/S +crablike +crack/ZSBYRDG +crackable/U +crackdown/MS +cracker/M +crackerjack/S +crackle/GJDS +crackling/M +crackly/RT +crackpot/SM +crackup/S +cradle/SRDGM +cradler/M +cradling/M +craft/MRDSG +craftily +craftiness/SM +craftsman/M +craftsmanship/SM +craftsmen +craftspeople +craftspersons +craftswoman +craftswomen +crafty/TRP +crag/SM +cragginess/SM +craggy/RTP +cram/S +crammed +crammer/M +cramming +cramp/MRDGS +cramper/M +crampon/SM +cranberry/SM +crane/DSGM +cranelike +cranial +cranium/MS +crank/SGTRDM +crankcase/MS +crankily +crankiness/MS +crankshaft/MS +cranky/TRP +cranny/DSGM +crap/SMDG! +crape/SM +crapped +crappie/M +crapping +crappy/RST +crapshooter/SM +crash/SRDGZ +crasher/M +crashing/Y +crass/TYRP +crassness/MS +crate/DSRGMZ +crater/DMG +cravat/SM +cravatted +cravatting +crave/DSRGJ +craven/SPYDG +cravenness/SM +craver/M +craving/M +craw/SYM +crawdad/S +crawfish's +crawl/RDSGZ +crawler/M +crawlspace/S +crawlway +crawly/TRS +crayfish/GSDM +crayon/GSDM +craze/GMDS +crazily +craziness/MS +crazy/SRTP +creak/SDG +creakily +creakiness/SM +creaky/PTR +cream/SMRDGZ +creamer/M +creamery/MS +creamily +creaminess/SM +creamy/TRP +crease's +crease/IDRSG +creased/CU +creases/C +creasing/C +create/XKVNGADS +created/U +creation/MAK +creationism/MS +creationist/MS +creative/YP +creativeness/SM +creativities +creativity's +creativity/K +creator/MS +creature/YMS +creatureliness/M +creaturely/P +credence/MS +credent +credential/SGMD +credenza/SM +credibility/IMS +credible/I +credibly/I +credit's +credit/EGBSD +creditability/M +creditable/P +creditableness/M +creditably/E +credited/U +creditor/MS +creditworthiness +credo/SM +credulity/ISM +credulous/IY +credulousness/SM +creed's +creed/C +creedal +creeds +creek/SM +creekside +creel/SMDG +creep/SGZR +creeper/M +creepily +creepiness/SM +creepy/PRST +cremate/XDSNG +cremation/M +crematoria +crematorium/MS +crematory/S +creme/S +crenelate/XGNSD +crenelation/M +creole/SM +creosote/MGDS +crepe/DSGM +crept +crescendo/SCM +crescendoed +crescendoing +crescent/MS +cress/S +crest/SGMD +crestfallen/PY +crestfallenness/M +cresting/M +crestless +cretaceous +cretin/MS +cretinism/MS +cretinous +cretonne/SM +crevasse/DSMG +crevice/SM +crew/DMGS +crewel/SM +crewelwork/SM +crewman/M +crewmen +crib/SM +cribbage/SM +cribbed +cribber/SM +cribbing/M +crick/GDSM +cricket/SMZRDG +cricketer/M +cried/C +crier/CM +cries/C +crime/GMDS +criminal/SYM +criminality/MS +criminalization/C +criminalize/GC +criminologist/SM +criminology/MS +crimp/RDGS +crimper/M +crimson/DMSG +cringe/SRDG +cringer/M +crinkle/DSG +crinkly/TRS +crinoline/SM +cripple/GMZDRS +crippler/M +crippling/Y +crises +crisis/M +crisp/PGTYRDS +crisper/M +crispiness/SM +crispness/MS +crispy/RPT +criss +crisscross/GDS +criteria +criterion/M +critic/MS +critical/YP +criticality +critically/U +criticalness/M +criticism/MS +criticize/GSRDZ +criticized/U +criticizer/M +criticizes/A +criticizing/UY +criticizingly/S +critique/MGSD +critter/SM +croak/SRDGZ +croaker/M +croaky/RT +crochet/RDSZJG +crocheter/M +crock/SGRDM +crockery/SM +crocodile/MS +crocus/SM +croft/MRGZS +crofter/M +croissant/MS +crone/SM +crony/SM +crook/SGDM +crooked/TPRY +crookedness/SM +crookneck/MS +croon/SRDGZ +crooner/M +crop/MS +cropland/MS +cropped +cropper/SM +cropping +croquet/MDSG +croquette/SM +crosier/SM +cross/ZTYSRDMPBJG +crossarm +crossbar/SM +crossbarred +crossbarring +crossbeam/MS +crossbones +crossbow/SM +crossbowman/M +crossbowmen +crossbred/S +crossbreed/SG +crosscheck/SGD +crosscurrent/SM +crosscut/SM +crosscutting +crossed/UA +crosses/UA +crossfire/SM +crosshatch/GDS +crossing/M +crossness/MS +crossover/MS +crosspatch/MS +crosspiece/SM +crosspoint +crossproduct/S +crossroad/GSM +crossroads/M +crosstalk/M +crosstown +crosswalk/MS +crossway/M +crosswind/SM +crosswise +crossword/MS +crotch/MDS +crotchet/MS +crotchetiness/M +crotchety/P +crotchless +crouch/DSG +croup/SMDG +croupier/M +croupy/TZR +crow/GDMS +crowbait +crowbar/SM +crowbarred +crowbarring +crowd/MRDSG +crowded/P +crowdedness/M +crowfeet +crowfoot/M +crown/RDMSJG +crowned/U +crowner/M +crozier's +croûton/MS +crucial/Y +crucible/MS +crucifiable +crucifix/SM +crucifixion/MS +cruciform/S +crucify/NGDS +crud/STMR +crudded +crudding +cruddy/TR +crude/YSP +crudeness/MS +crudity/MS +crudités +cruel/YRTSP +cruelness/MS +cruelty/SM +cruet/MS +cruft +crufty +cruise/GZSRD +cruiser/M +cruller/SM +crumb/GSYDM +crumble/DSJG +crumbliness/MS +crumbly/PTRS +crumby/RT +crumminess/S +crummy/SRTP +crump +crumpet/SM +crumple/DSG +crunch/DSRGZ +crunchiness/MS +crunchy/TRP +crupper/MS +crusade/GDSRMZ +crusader/M +cruse/MS +crush/SRDBGZ +crushable/U +crusher/M +crushing/Y +crushproof +crust/GMDS +crustacean/MS +crustal +crustily +crustiness/SM +crusty/SRTP +crutch/MDSG +crux/MS +cry/JGDRSZ +crybaby/MS +cryogenic/S +cryogenics/M +cryostat/M +cryosurgery/SM +crypt's +crypt/CS +cryptanalysis/M +cryptanalyst/M +cryptanalytic +cryptic +cryptically +cryptogram/MS +cryptographer/MS +cryptographic +cryptographically +cryptography/MS +cryptologic +cryptological +cryptologist/M +cryptology/M +crystal/SM +crystalline/S +crystallite/SM +crystallization/AMS +crystallize/SRDZG +crystallized/UA +crystallizes/A +crystallizing/A +crystallographer/MS +crystallographic +crystallography/M +crèche/SM +cs's +cs/EA +ct +ctn +ctr +cu/DG +cub/MDRSZG +cubbed +cubbing +cubbyhole/MS +cube/SM +cuber/M +cubic/YS +cubical/Y +cubicle/SM +cubism/SM +cubist/MS +cubit/MS +cuboid +cuckold/GSDM +cuckoldry/MS +cuckoo/SGDM +cucumber/MS +cud/MS +cuddle/GSD +cuddly/TRP +cudgel/GSJMD +cue/MS +cuff/GSDM +cuisine/MS +culinary +cull/DRGS +cullender's +culler/M +culminate/XSDGN +culmination/M +culotte/S +culpa/SM +culpability/MS +culpable/I +culpableness/M +culpably +culprit/SM +cult/MS +cultism/SM +cultist/SM +cultivable +cultivate/XBSDGN +cultivated/U +cultivation/M +cultivator/SM +cultural/Y +culture/SDGM +cultured/U +culvert/SM +cum/S +cumber/DSG +cumbersome/YP +cumbersomeness/MS +cumbrous +cumin/MS +cummerbund/MS +cumquat's +cumulate/XVNGSD +cumulation/M +cumulative/Y +cumuli +cumulonimbi +cumulonimbus/M +cumulus/M +cuneiform/S +cunnilingus/SM +cunning/RYSPT +cunningness/M +cunt/SM! +cup/MS +cupboard/SM +cupcake/SM +cupful/SM +cupid/S +cupidinously +cupidity/MS +cupola/MDGS +cupped +cupping/M +cupric +cuprous +cur's +cur/IBS +curability/MS +curable/IP +curableness/MI +curably/I +curacy/SM +curare/MS +curate/VGMSD +curative/YS +curator/KMS +curatorial +curb/SJDMG +curbing/M +curbside +curbstone/MS +curd/SMDG +curdle/SDG +cure/KBDRSGZ +cured/U +curer/MK +curettage/SM +curfew/SM +curfs +curia/M +curiae +curie/SM +curio/SM +curiosity/SM +curious/TPRY +curiousness/SM +curium/MS +curl/UDSG +curler/SM +curlew/MS +curlicue/MGDS +curliness/SM +curling/M +curly/PRT +curlycue's +curmudgeon/MYS +currant/SM +curred/AFI +currency's +currency/SF +current/FSY +currently/A +currentness/M +curricle/M +curricula +curricular +curriculum/M +currier/M +curring/FAI +curry/RSDMG +currycomb/DMGS +curs/ASDVG +curse's +curse/A +cursed/YRPT +cursedness/M +cursive/EPYA +cursiveness/EM +cursives +cursor/DMSG +cursorily +cursoriness/SM +cursory/P +curt/TYRP +curtail/LSGDR +curtailer/M +curtailment/SM +curtain/GSMD +curtness/MS +curtsey's +curtsy/SDMG +curvaceous/YP +curvaceousness/S +curvature/MS +curve/DSGM +curved's +curved/A +curvilinear/Y +curvilinearity/M +curving/M +curvy/RT +cushion/SMDG +cushy/TR +cusp/MS +cuspid/MS +cuspidor/MS +cuss's +cuss/EGDSR +cussed/YP +cussedness/M +cusses/F +cussing/F +custard/MS +custodial +custodian/SM +custodianship/MS +custody/MS +custom/SMRZ +customarily +customariness/M +customary/PS +customer/M +customhouse/S +customization/SM +customize/ZGBSRD +cut/MRST +cutaneous/Y +cutaway/SM +cutback/SM +cute/SPY +cuteness/MS +cutesy/RT +cuticle/SM +cutlass/MS +cutler/SM +cutlery/MS +cutlet/SM +cutoff/MS +cutout/SM +cutter/SM +cutthroat/SM +cutting/MYS +cuttle/M +cuttlebone/SM +cuttlefish/MS +cutup/MS +cutworm/MS +cw +cwt +cyan/MS +cyanate/M +cyanic +cyanide/GMSD +cyanogen/M +cybernetic/S +cybernetics/M +cyberpunk/S +cyberspace/S +cyborg/S +cyclamen/MS +cycle's +cycle/ASDG +cycler +cycleway/S +cyclic +cyclical/SY +cycling/M +cyclist/MS +cyclohexanol +cycloid/SM +cycloidal +cyclometer/MS +cyclone/SM +cyclonic +cyclopean +cyclopedia/MS +cyclopes +cyclops +cyclotron/MS +cyder/SM +cygnet/MS +cylinder/GMDS +cylindric +cylindrical/Y +cymbal/SM +cymbalist/MS +cynic/MS +cynical/UY +cynicism/MS +cynosure/SM +cypher/MGSD +cypreses +cypress/SM +cyst/MS +cystic +cytochemistry/M +cytochrome/M +cytologist/MS +cytology/MS +cytolysis/M +cytoplasm/SM +cytoplasmic +cytosine/MS +cytotoxic +czar/SM +czarevitch/M +czarina/SM +czarism/M +czarist/S +czarship +d'Arezzo +d'Estaing +d'art +d'etat +d'etre +d'oeuvre +d's/A +d/JGVX +dB/M +dab/S +dabbed +dabber/MS +dabbing +dabble/RSDZG +dabbler/M +dace/MS +dacha/SM +dachshund/SM +dactyl/MS +dactylic/S +dad/SM +dadaism/S +dadaist/S +daddy/SM +dado/DMG +dadoes +daemon/SM +daemonic +daffiness/S +daffodil/MS +daffy/PTR +daft/TYRP +daftness/MS +dagger/DMSG +daguerreotype/MGDS +dahlia/MS +dailiness/MS +daily/PS +daintily +daintiness/MS +dainty/TPRS +daiquiri/SM +dairy/MJGS +dairying/M +dairyland +dairymaid/SM +dairyman/M +dairymen +dairywoman/M +dairywomen +dais/SM +daisy/SM +dale/SMH +daleth/M +dalliance/SM +dallier/M +dally/ZRSDG +dalmatian/S +dam/MDS +damage/MZGRSD +damageable +damaged/U +damager/M +damaging/Y +damask/DMGS +dame/SM +dammed +damming +dammit/S +damn/GSBRD +damnably +damnation/MS +damned/TR +damnedest/MS +damning/Y +damp/SGZTXYRDNP +damped/U +dampen/RDZG +dampener/M +damper/M +dampness/MS +damsel/MS +damselfly/MS +damson/MS +dance/SRDJGZ +dancelike +dancer/M +dandelion/MS +dander/DMGS +dandify/SDG +dandily +dandle/GSD +dandruff/MS +dandy/TRSM +dang/SGZRD +danger/DMG +dangerous/YP +dangerousness/M +dangle/ZGRSD +dangler/M +dangling/Y +danish/S +dank/TPYR +dankness/MS +danseuse/SM +dapper/PSTRY +dapperness/M +dapple/SDG +dare/ZGDRSJ +daredevil/MS +daredevilry/S +darer/M +daresay +daring/PY +daringness/M +dark/GTXYRDNSP +darken/RDZG +darkener/M +darkish +darkly/TR +darkness/MS +darkroom/SM +darling/YMSP +darlingness/M +darn/GRDZS +darned/TR +darner/M +darning/M +dart/MRDGZS +dartboard/SM +darter/M +dash/GZSRD +dashboard/SM +dasher/M +dashiki/SM +dashing/Y +dastard/MYS +dastardliness/SM +dastardly/P +data/M +database/DSMG +datafile +datagram/MS +dataset/S +date/DRSMZGV +dated/U +datedly +datedness +dateless +dateline/DSMG +dater/M +dative/S +datum/MS +daub/RDSGZ +dauber/M +daughter/MYS +daunt/DSG +daunted/U +daunting/Y +dauntless/PY +dauntlessness/SM +dauphin/SM +davenport/MS +davit/SM +dawdle/ZGRSD +dawdler/M +dawn/GSDM +day/SM +daybed/S +daybreak/SM +daycare/S +daydream/RDMSZG +daydreamer/M +daylight/GSDM +daysack +daytime/SM +daze/DSG +dazed/PY +dazzle/ZGJRSD +dazzler/M +dazzling/Y +db +dbl +deacon/DSMG +deaconess/MS +dead/PTXYRN +deadbeat/SM +deadbolt/S +deaden/RDG +deadener/M +deadening/MY +deadhead/MS +deadline/MGDS +deadliness/SM +deadlock/MGDS +deadly/RPT +deadness/M +deadpan/S +deadpanned +deadpanner +deadpanning +deadwood/SM +deaf/TXPYRN +deafen/JGD +deafening/MY +deafness/MS +deal/RSGZJ +dealer/M +dealership/MS +dealing/M +deallocator +dealt +dean/DMG +deanery/MS +deanship/SM +dear/TYRHPS +dearness/MS +dearth/M +dearths +deary/MS +deassign +death/MY +deathbed/MS +deathblow/SM +deathless/Y +deathlike +deathly/TR +deaths +deathtrap/SM +deathward +deathwatch/MS +deb/MS +debacle/SM +debar/L +debark/G +debarkation/SM +debarment/SM +debarring +debaser/M +debatable/U +debate/BMZ +debater/M +debauch/GDRS +debauched/PY +debauchedness/M +debauchee/SM +debaucher/M +debauchery/SM +debenture/MS +debilitate/NGXSD +debilitation/M +debility/MS +debit/DG +debonair/PY +debonairness/SM +debouch/DSG +debrief/GJ +debris/M +debt/SM +debtor/SM +debut/MDG +decade/MS +decadency/S +decadent/YS +decaf/S +decaffeinate/DSG +decagon/MS +decal/SM +decamp/L +decampment/MS +decapitate/GSD +decapitator/SM +decathlon/SM +decay/GRD +decease/M +decedent/MS +deceit/SM +deceitful/PY +deceitfulness/SM +deceive/ZGRSD +deceived/U +deceiver/M +deceives/U +deceiving/U +deceivingly +decelerate/XNGSD +deceleration/M +decelerator/SM +decency/ISM +decennial/SY +decent/TIYR +deception/SM +deceptive/YP +deceptiveness/SM +decertify/N +dechlorinate/N +decibel/MS +decidability/U +decidable/U +decide/GRSDB +decided/PY +decidedness/M +deciduous/YP +deciduousness/M +decile/SM +deciliter/SM +decimal/SYM +decimate/XNGDS +decimation/M +decimeter/MS +decipher/BRZG +decipherable/IU +decipherer/M +decision/ISM +decisional +decisioned +decisioning +decisive/IPY +decisiveness/MSI +deck/GRDMSJ +deckchair +decker/M +deckhand/S +decking/M +declamation/SM +declamatory +declarable +declaration's/A +declaration/MS +declarative/SY +declarator/MS +declaratory +declare/AGSD +declared/U +declarer/MS +declension/SM +declination/MS +decline/ZGRSD +decliner/M +declivity/SM +deco +decolletes +decolorising +decomposability/M +decomposable/IU +decompose/B +decompress/R +decongestant/S +deconstruction +deconvolution +decor/S +decorate/NGVDSX +decorated/AU +decorates/A +decorating/A +decoration/ASM +decorative/YP +decorativeness/M +decorator/SM +decorous/PIY +decorousness's/I +decorousness/MS +decorticate/GNDS +decortication/M +decorum/MS +decoupage/MGSD +decouple/G +decoy/M +decrease +decreasing/Y +decree/RSM +decreeing +decrement/DMGS +decremental +decrepit +decrepitude/SM +decriminalization/S +decriminalize/DS +decry/G +decrypt/GD +decryption +decustomised +dedicate/AGDS +dedicated/Y +dedication/MS +dedicative +dedicator/MS +dedicatory +deduce/RSDG +deducible +deduct/VG +deductibility/M +deductible/S +deduction/SM +deductive/Y +deed's +deed/IS +deeded +deeding +deejay/MDSG +deem/ADGS +deemphasis +deep/PTXSYRN +deepen/DG +deepish +deepness/MS +deer/SM +deerskin/MS +deerstalker/SM +deerstalking/M +def/Z +deface/LZ +defacement/SM +defaecate +defalcate/NGXSD +defalcation/M +defamation/SM +defamatory +defame/ZR +defamer/M +default/ZR +defaulter/M +defeat/ZGD +defeated/U +defeater/M +defeatism/SM +defeatist/SM +defecate/DSNGX +defecation/M +defect/MDSVG +defection/SM +defective/PYS +defectiveness/MS +defector/MS +defendant/SM +defended/U +defenestrate/GSD +defense/VGSDM +defenseless/PY +defenselessness/MS +defenses/U +defensibility/M +defensible/I +defensibly/I +defensive/PSY +defensiveness/MS +deference/MS +deferent/S +deferential/Y +deferrable +deferral/SM +deferred +deferrer/MS +deferring +deffer +defiance/MS +defiant/Y +defibrillator/M +deficiency/MS +deficient/SY +deficit/MS +defier/M +defile/L +defilement/MS +definable/UI +definably/I +define/AGDRS +defined/U +definer/SM +definite/IPY +definiteness/IMS +definition/ASM +definitional +definitive/SYP +definitiveness/M +defis +deflate/XNGRSDB +deflation/M +deflationary +deflect/DSGV +deflected/U +deflection/MS +deflector/MS +defocus +defocussing +defog +defogger/S +defoliant/SM +defoliator/SM +deform/B +deformational +deformed/U +deformity/SM +defraud/ZGDR +defrauder/M +defrayal/SM +defrost/RZ +defroster/M +deft/TYRP +deftness/MS +defunct/S +defy/RDG +defying/Y +deg +degassing +degauss/GD +degeneracy/MS +degenerate/PY +degenerateness/M +degrade/B +degraded/YP +degradedness/M +degrading/Y +degrease +degree/SM +degum +dehumanize +dehydrator/MS +deice/ZR +deicer/M +deictic +deification/M +deify/SDXGN +deign/DGS +deist/SM +deistic +deity/SM +deja +deject/DSG +dejected/PY +dejectedness/M +dejection/SM +delay/D +delayer/G +delectable/SP +delectableness/M +delectably +delectation/MS +delegable +delete/XBRSDNG +deleted/U +deleterious/PY +deleteriousness/M +deletion/M +delfs +delft/MS +delftware/S +deli/SM +deliberate/PVY +deliberateness/SM +deliberative/PY +deliberativeness/M +delicacy/IMS +delicate/IYP +delicateness/IM +delicatenesses +delicates +delicatessen/MS +delicious/YSP +deliciousness/MS +delicti +delighted/YP +delightedness/M +delightful/YP +delightfulness/M +delineate/SDXVNG +delineation/M +delinquency/MS +delinquent/SYM +deliquesce/GSD +deliquescent +delirious/PY +deliriousness/MS +delirium/SM +deliver/AGSD +deliverable/U +deliverables +deliverance/SM +delivered/U +deliverer/SM +delivery/AM +deliverymen/M +dell/SM +delphinium/SM +delta/MS +deltoid/SM +delude/RSDG +deluder/M +deluding/Y +deluge/SDG +delusion/SM +delusional +delusive/PY +delusiveness/M +deluxe +delve/GZSRD +delver/M +demagnify/N +demagogic +demagogue/GSDM +demagoguery/SM +demagogy/MS +demand/GSRD +demander/M +demanding/U +demandingly +demarcate/SDNGX +demarcation/M +demean/GDS +demeanor/SM +demented/YP +dementedness/M +dementia/MS +demesne/SM +demigod/MS +demijohn/MS +demimondaine/SM +demimonde/SM +demineralization/SM +demise/DMG +demit +demitasse/MS +demitted +demitting +demo/DMPG +democracy/MS +democrat/SM +democratic/U +democratically/U +democratization/MS +democratize/DRSG +democratizes/U +demographer/MS +demographic/S +demographical/Y +demography/MS +demolish/GSRD +demolisher/M +demolition/MS +demon/SM +demonetization/S +demoniac/S +demoniacal/Y +demonic +demonology/M +demonstrable/I +demonstrableness/M +demonstrably/I +demonstrate/XDSNGV +demonstration/M +demonstrative/YUP +demonstrativeness/UM +demonstrativenesses +demonstratives +demonstrator/MS +demoralization/M +demoralizer/M +demoralizing/Y +demote/DGX +demotic/S +demount/B +demulcent/S +demultiplex +demur/RTS +demure/YP +demureness/SM +demurral/MS +demurred +demurrer/MS +demurring +demythologization/M +demythologize/R +den +dendrite/MS +dengue/MS +deniable/U +denial/SM +denier/M +denigrate/VNGXSD +denigration/M +denim/SM +denizen/SMDG +denned +denning +denominate/V +denominational/Y +denote/B +denouement/MS +denounce/LZRSDG +denouncement/SM +denouncer/M +dens/RT +dense/FR +densely +denseness/SM +densitometer/MS +densitometric +densitometry/M +density/MS +dent's +dent/ISGD +dental/YS +dentifrice/SM +dentin/SM +dentine's +dentist/SM +dentistry/MS +dentition/MS +denture/IMS +denuclearize/GSD +denudation/SM +denude/DG +denuder/M +denunciate/VNGSDX +denunciation/M +deny/SRDZG +denying/Y +deodorant/SM +deodorization/SM +deodorize/GZSRD +deodorizer/M +deoxyribonucleic +depart/L +department/MS +departmental/Y +departmentalization/SM +departmentalize/DSG +departure/MS +depend/B +dependability/MS +dependable/P +dependableness/M +dependably +dependence/ISM +dependency/MS +dependent's +dependent/IYS +depict/RDSG +depicted/U +depicter/M +depiction/SM +depilatory/S +deplete/VGNSDX +depletion/M +deplorable/P +deplorableness/M +deplorably +deplore/SRDBG +deplorer/M +deploring/Y +deploy/AGDLS +deployable +deployment/SAM +depolarize +deponent/S +deport/LG +deportation/MS +deportee/SM +deportment/MS +depose +deposit/ADGS +depositary/M +deposition/A +depositor/SAM +depository/MS +deprave/GSRD +depraved/PY +depravedness/M +depraver/M +depravity/SM +deprecate/XSDNG +deprecating/Y +deprecation/M +deprecatory +depreciable +depreciate/XDSNGV +depreciating/Y +depreciation/M +depreciative/Y +depress/V +depressant/S +depressible +depression/MS +depressive/YS +depressor/MS +deprive/GSD +depth/M +depths +deputation/SM +depute/SDG +deputize/DSG +deputy/MS +dequeue +derail/L +derailment/MS +derange/L +derangement/MS +derby/SM +dereference/Z +derelict/S +dereliction/SM +deride/D +deriding/Y +derision/SM +derisive/PY +derisiveness/MS +derisory +derivable/U +derivate/XNV +derivation/M +derivative/SPYM +derivativeness/M +derive/B +derived/U +dermal +dermatitides +dermatitis/MS +dermatological +dermatologist/MS +dermatology/MS +dermis/SM +derogate/XDSNGV +derogation/M +derogatorily +derogatory +derrick/SMDG +derringer/SM +derrière/S +dervish/SM +desalinate/NGSDX +desalination/M +desalinization/MS +desalinize/GSD +desalt/G +descant/M +descend/ZGSDR +descendant/SM +descended/FU +descendent's +descender/M +descending/F +descends/F +descent +describable/I +describe/ZB +description/MS +descriptive/SYP +descriptiveness/MS +descriptor/SM +descry/SDG +desecrate/SRDGNX +desecrater/M +desecration/M +desert/ZGMRDS +deserter/M +desertification +desertion/MS +deserve/J +deserved/YU +deservedness/M +deserving/Y +desiccant/S +desiccate/XNGSD +desiccation/M +desiccator/SM +desiderata +desideratum/M +design/ADGS +designable +designate/VNGSDX +designation/M +designational +designator/SM +designed/Y +designer/M +designing/U +desirabilia +desirability's +desirability/US +desirable/UPS +desirableness's/U +desirableness/SM +desirably/U +desire/BR +desired/U +desirer/M +desirous/PY +desirousness/M +desist/DSG +desk/SM +desktop/S +desolate/PXDRSYNG +desolateness/SM +desolater/M +desolating/Y +desolation/M +desorption/M +despair/SGDR +despairer/M +despairing/Y +desperado/M +desperadoes +desperate/YNXP +desperateness/SM +desperation/M +despicable +despicably +despise/SRDG +despiser/M +despoil/L +despoilment/MS +despond +despondence/S +despondency/MS +despondent/Y +despotic +despotically +despotism/SM +dessert/SM +dessicate/DN +destinate/NX +destination/M +destine/GSD +destiny/MS +destitute/NXP +destituteness/M +destitution/M +destroy/BZGDRS +destroyer/M +destruct/VGSD +destructibility/SMI +destructible/I +destruction/SM +destructive/YP +destructiveness/MS +destructor/M +desuetude/MS +desultorily +desultoriness/M +desultory/P +detach/LSRDBG +detached/YP +detachedness/M +detacher/M +detachment/SM +detailed/YP +detailedness/M +detain/LGRDS +detainee/S +detainer/M +detainment/MS +detect/DBSVG +detectability/U +detectable/U +detectably/U +detected/U +detection/SM +detective/MS +detector/MS +detentes +detention/SM +deter/SL +detergency/M +detergent/SM +deteriorate/XDSNGV +deterioration/M +determent/SM +determinability/M +determinable/IP +determinableness/IM +determinacy/I +determinant/MS +determinate/PYIN +determinateness/IM +determination/IM +determinative/P +determinativeness/M +determine/GASD +determined/U +determinedly +determinedness/M +determiner/SM +determinism's/I +determinism/MS +deterministic/I +deterministically +deterred/U +deterrence/SM +deterrent/SMY +deterring +deters/V +detersive/S +detestable/P +detestableness/M +detestably +detestation/SM +dethrone/L +dethronement/SM +detonable +detonate/XDSNGV +detonated/U +detonation/M +detonator/MS +detour/G +detox/SDG +detoxification/M +detoxify/NXGSD +detract/GVD +detractive/Y +detribalize/GSD +detriment/SM +detrimental/SY +detritus/M +deuce/SDGM +deuced/Y +deus +deuterium/MS +deuteron/M +devastate/XVNGSD +devastating/Y +devastation/M +devastator/SM +develop/ALZSGDR +developed/U +developer/MA +development/ASM +developmental/Y +deviance/MS +deviancy/S +deviant/YMS +deviate/XSDGN +deviated/U +deviating/U +deviation/M +devil/SLMDG +devilish/PY +devilishness/MS +devilment/SM +devilry/MS +deviltry/MS +devious/YP +deviousness/SM +devise/JR +deviser/M +devoice +devolution/MS +devolve/GSD +devote/XN +devoted/Y +devotee/MS +devotion/M +devotional/YS +devour/SRDZG +devourer/M +devout/PRYT +devoutness/MS +dew/MDGS +dewar +dewberry/MS +dewclaw/SM +dewdrop/MS +dewiness/MS +dewlap/MS +dewy/TPR +dexes/I +dexter +dexterity/MS +dexterous/PY +dexterousness/MS +dextrose/SM +dhoti/SM +dhow/MS +diabase/M +diabetes/M +diabetic/S +diabolic +diabolical/YP +diabolicalness/M +diabolism/M +diachronic/P +diacritic/MS +diacritical/YS +diadem/GMDS +diaereses +diaeresis/M +diagnometer/SM +diagnosable/U +diagnose/BGDS +diagnosed/U +diagnosis/M +diagnostic/MS +diagnostically +diagnostician/SM +diagnostics/M +diagonal/YS +diagonalize/GDSB +diagram/MS +diagrammable +diagrammatic +diagrammaticality +diagrammatically +diagrammed +diagrammer/SM +diagramming +dial/MRDSGZJ +dialect/MS +dialectal/Y +dialectic/MS +dialectical/Y +dialed/A +dialer/M +dialing/M +dialog/MS +dialogged +dialogging +dialogue/DS +dials/A +dialysis/M +dialyzed/U +dialyzes +diam +diamagnetic +diameter/MS +diametric +diametrical/Y +diamond/GSMD +diamondback/SM +diapason/MS +diaper/SGDM +diaphanous/YP +diaphanousness/M +diaphragm/SM +diaphragmatic +diarist/SM +diarrhea/MS +diarrheal +diary/MS +diaspora +diastase/SM +diastole/MS +diastolic +diathermy/SM +diathesis/M +diatom/SM +diatomic +diatonic +diatribe/MS +dibble/SDMG +dibs +dice/GDRS +dicer/M +dicey +dichloride/M +dichotomization/M +dichotomize/DSG +dichotomous/PY +dichotomy/SM +dicier +diciest +dicing/M +dick/GZXRDMS! +dickens/M +dicker/DG +dickey/SM +dickier +dickiest +dicky's +dicotyledon/SM +dicotyledonous +dicta/M +dictate/SDNGX +dictation/M +dictator/MS +dictatorial/YP +dictatorialness/M +dictatorship/SM +diction/MS +dictionary/SM +dictum/M +did/AU +didactic/S +didactically +didactics/M +diddle/ZGRSD +diddler/M +didn't +dido/M +didoes +didst +die/DS +dieing +dielectric/MS +diem +diereses +dieresis/M +dies's +dies/U +diesel/GMDS +diet/RDGZSM +dietary/S +dieter/M +dietetic/S +dietetics/M +diethylaminoethyl +diethylstilbestrol/M +dietitian/MS +differ/SZGRD +difference's/I +difference/DSGM +differences/I +different/YI +differentiability +differentiable +differential/SMY +differentiate/XSDNG +differentiated/U +differentiation/M +differentiator/SM +differentness +difficile +difficult/Y +difficulty/SM +diffidence/MS +diffident/Y +diffract/GSD +diffraction/SM +diffractometer/SM +diffuse/PRSDZYVXNG +diffuseness/MS +diffuser/M +diffusible +diffusion/M +diffusional +diffusive/YP +diffusiveness/M +diffusivity/M +dig/TS +digerati +digest/RDVGS +digested/IU +digester/M +digestibility/MS +digestible/I +digestifs +digestion/ISM +digestive/YSP +digger/MS +digging/S +digit/SM +digital/SY +digitalis/M +digitalization/MS +digitalized +digitalizes +digitalizing +digitization/M +digitize/ZGDRS +digitizer/M +dignified/U +dignify/DSG +dignitary/SM +dignity/ISM +digram +digraph/M +digraphs +digress/GVDS +digression/SM +digressive/PY +digressiveness/M +dihedral +dike/DRSMG +diker/M +diktat/SM +dilapidate/XGNSD +dilapidation/M +dilatation/SM +dilate/XVNGSD +dilated/YP +dilation/M +dilator/SM +dilatoriness/M +dilatory/P +dilemma/MS +dilettante/MS +dilettantish +dilettantism/MS +diligence/SM +diligent/YP +diligentness/M +dilithium +dill/SGMD +dilling/R +dillis +dilly/SM +dillydally/GSD +dilogarithm +diluent +dilute/RSDPXYVNG +diluted/U +diluteness/M +dilution/M +dim/RYPZS +dime/SM +dimension/MDGS +dimensional/Y +dimensionality/M +dimensionless +dimer/M +dimethyl/M +dimethylglyoxime +diminish/SDGBJ +diminished/U +diminuendo/SM +diminution/SM +diminutive/SYP +diminutiveness/M +dimity/MS +dimmed/U +dimmer/MS +dimmest +dimming +dimness/SM +dimorphism/M +dimple/MGSD +dimply/RT +dimwit/MS +dimwitted +din/MDRZGS +dinar/SM +dine/S +diner/M +dinette/MS +ding/GD +dingbat/MS +dinghy/SM +dingily +dinginess/SM +dingle/MS +dingo/MS +dingoes +dingus/SM +dingy/PRST +dinky/RST +dinned +dinner/SM +dinnertime/S +dinnerware/MS +dinning +dinosaur/MS +dint/SGMD +diocesan/S +diocese/SM +diode/SM +diopter/MS +diorama/SM +dioxalate +dioxide/MS +dioxin/S +dip/S +diphtheria/SM +diphthong/SM +diplexers +diploid/S +diploma/SMDG +diplomacy/SM +diplomat/MS +diplomata +diplomatic/S +diplomatically +diplomatics/M +diplomatist/SM +dipodic +dipody/M +dipole/MS +dipped +dipper/SM +dipping/S +dippy/TR +dipsomania/SM +dipsomaniac/MS +dipstick/MS +dipterous +diptych/M +diptychs +dire/YTRP +direct/RDYPTSVG +directed/IUA +direction/MIS +directional/SY +directionality +directions/A +directive/SM +directivity/M +directly/I +directness/ISM +director/AMS +directorate/SM +directorial +directorship/SM +directory/SM +directrix/MS +directs/IA +direful/Y +direness/M +dirge/GSDM +dirigible/S +dirk/GDMS +dirndl/MS +dirt/MS +dirtily +dirtiness/SM +dirty/GPRSDT +dis/MB +disable/LZGD +disablement/MS +disabler/M +disabuse +disadvantaged/P +disagreeable/S +disallow/D +disambiguate/DSGNX +disappointed/Y +disappointing/Y +disarming/Y +disarrange/L +disastrous/Y +disband/L +disbandment/SM +disbar/L +disbarment/MS +disbarring +disbelieving/Y +disbursal/S +disburse/GDRSL +disbursement/MS +disburser/M +disc/GDM +discern/SDRGL +discerner/M +discernibility +discernible/I +discernibly +discerning/Y +discernment/MS +discharged/U +disciple/DSMG +discipleship/SM +disciplinarian/SM +disciplinary +discipline/IDM +disciplined/U +discipliner/M +disciplines +disciplining +disclosed/U +disco/MG +discography/MS +discolor/G +discolored/MP +discoloreds/U +discombobulate/SDGNX +discomfit/DG +discomfiture/MS +discommode/DG +disconcerting/Y +disconnect/R +disconnected/P +disconnectedness/S +disconnecter/M +disconsolate/YN +discord/G +discordance/SM +discordant/Y +discorporate/D +discotheque/MS +discount/B +discourage/LGDR +discouragement/MS +discouraging/Y +discover/ADGS +discoverable/I +discovered/U +discoverer/S +discovery/SAM +discreet/TRYP +discreetly/I +discreetness's/I +discreetness/SM +discrepancy/SM +discrepant/Y +discrete/YPNX +discreteness/SM +discretion/IMS +discretionary +discretization +discretized +discriminable +discriminant/MS +discriminate/SDVNGX +discriminated/U +discriminating/YI +discrimination/MI +discriminator/MS +discriminatory +discursiveness/S +discus/SM +discussant/MS +discussed/UA +discusser/M +discussion/SM +disdain/MGSD +disdainful/YP +disdainfulness/M +disease/G +disembowel/SLGD +disembowelment/SM +disengage/L +disfigure/L +disfigurement/MS +disfranchise/L +disfranchisement/MS +disgorge +disgrace/R +disgracer/M +disgruntle/DSLG +disgruntlement/MS +disguise/R +disguised/UY +disguiser/M +disgust +disgusted/Y +disgustful/Y +disgusting/Y +dish/GD +dishabille/SM +disharmonious +dishcloth/M +dishcloths +dishevel/LDGS +dishevelment/MS +dishonest +dishonored/U +dishpan/MS +dishrag/SM +dishtowel/SM +dishwasher/MS +dishwater/SM +disillusion/LGD +disillusionment/SM +disinfectant/MS +disinherit +disinterested/P +disinterestedness/SM +disinvest/L +disjoin +disjointedness/S +disjunct/VS +disjunctive/YS +disk/D +diskette/S +dislike/G +dislodge/LG +dislodgement/M +dismal/PSTRY +dismalness/M +dismantle/L +dismantlement/SM +dismay/D +dismayed/U +dismaying/Y +dismember/LG +dismemberment/MS +dismiss/RZ +dismissive/Y +disoblige/G +disorder/Y +disordered/YP +disorderedness/M +disorderliness/M +disorderly/P +disorganize +disorganized/U +disparage/RSDLG +disparagement/MS +disparager/M +disparaging/Y +disparate/PSY +disparateness/M +dispatch/Z +dispel/S +dispelled +dispelling +dispensable/I +dispensary/MS +dispensate/NX +dispensation/M +dispense/ZGDRSB +dispenser/M +dispersal/MS +dispersant/M +disperse/XDRSZLNGV +dispersed/Y +disperser/M +dispersible +dispersion/M +dispersive/PY +dispersiveness/M +dispirit/DSG +displace/L +display/AGDS +displayed/U +displease/G +displeased/Y +displeasure +disport +disposable/S +disposal/SM +dispose/IGSD +disposition/ISM +dispositional +disproportional +disproportionate/N +disproportionation/M +disprove/B +disputable/I +disputably/I +disputant/SM +disputation/SM +disputatious/Y +dispute/ZBGSRD +disputed/U +disputer/M +disquiet/M +disquieting/Y +disquisition/SM +disregardful +disrepair/M +disreputable/P +disreputableness/M +disrepute/M +disrespect +disrupt/GVDRS +disrupted/U +disrupter/M +disruption/MS +disruptive/YP +disruptor/M +dissatisfy +dissect/DG +dissed +dissemble/ZGRSD +dissembler/M +disseminate/XGNSD +dissemination/M +dissension/SM +dissent/ZGSDR +dissenter/M +dissertation/SM +disservice +disses +dissever +dissidence/SM +dissident/MS +dissimilar/S +dissing +dissipate/XRSDVNG +dissipated/U +dissipatedly +dissipatedness/M +dissipater/M +dissipation/M +dissociable/I +dissociate/DSXNGV +dissociated/U +dissociation/M +dissociative/Y +dissoluble/I +dissolute/PY +dissoluteness/SM +dissolve/ASDG +dissolved/U +dissonance/SM +dissonant/Y +dissuade/GDRS +dissuader/M +dissuasive +dist +distaff/SM +distal/Y +distance/DSMG +distant/YP +distantness/M +distaste +distemper +distend +distension +distention/SM +distillate/XNMS +distillation/M +distillery/MS +distinct/IYVP +distincter +distinctest +distinction/MS +distinctive/YP +distinctiveness/MS +distinctness/MSI +distinguish/BDRSG +distinguishable/I +distinguishably/I +distinguished/U +distinguisher/M +distort/BGDR +distorted/U +distorter/M +distortion/MS +distract/DG +distracted/YP +distractedness/M +distracting/Y +distrait +distraught/Y +distress +distressful +distressing/Y +distribute/ADXSVNGB +distributed/U +distributer +distribution/AM +distributional +distributive/SPY +distributiveness/M +distributivity +distributor/SM +distributorship/M +district's +district/GSAD +distrust/G +disturb/ZGDRS +disturbance/SM +disturbed/U +disturber/M +disturbing/Y +disulfide/M +disuse/M +disyllable/M +ditch/MRSDG +ditcher/M +dither/RDZSG +ditsy/TR +ditto/DMGS +ditty/SDGM +ditz/S +diuresis/M +diuretic/S +diurnal/SY +div/TZGJDRS +diva/MS +divalent/S +divan/SM +dive/S +dived/M +diver/M +diverge/SDG +divergence/SM +divergent/Y +diverse/XYNP +diverseness/MS +diversification/M +diversifier/M +diversify/GSRDNX +diversion/M +diversionary +diversity/SM +divert/GSD +diverticulitis/SM +divertimento/M +divest/LDGS +divestiture/MS +divestment/S +dividable +divide/AGDS +divided/U +dividend/MS +divider/MS +divination/SM +divine/RSDTZYG +diviner/M +divinity/MS +divisibility/IMS +divisible/I +division/SM +divisional +divisive/PY +divisiveness/MS +divisor/SM +divorce/GSDLM +divorcement/MS +divorcée/MS +divot/MS +divulge/GSD +divvy/GSDM +dixieland +dizzily +dizziness/SM +dizzy/PGRSDT +dizzying/Y +djellaba/S +djellabah's +do/TZRHGJ +doable +dobbin/MS +doc/MS +docent/SM +docile/Y +docility/MS +dock/GZSRDM +docker/M +docket/GSMD +dockland/MS +dockside/M +dockworker/S +dockyard/SM +doctor/GSDM +doctoral +doctorate/SM +doctrinaire/S +doctrinal/Y +doctrine/SM +docudrama/S +document/RDMZGS +documentary/MS +documentation/MS +documented/U +dodder/DGS +dodecahedra +dodecahedral +dodecahedron/M +dodge/GZSRD +dodgem/S +dodger/M +dodo/SM +doe/MS +doer/MU +does/AU +doeskin/MS +doesn't +doff/SGD +dog/SM +dogcart/SM +dogcatcher/MS +doge/SM +dogeared +dogfight/GMS +dogfish/SM +dogfought +dogged/PY +doggedness/SM +doggerel/SM +dogging +doggone/RSDTG +doggy/SRMT +doghouse/SM +dogie/SM +dogleg/SM +doglegged +doglegging +dogma/MS +dogmatic/S +dogmatically/U +dogmatics/M +dogmatism/SM +dogmatist/SM +dogsbody/M +dogtooth/M +dogtrot/MS +dogtrotted +dogtrotting +dogwood/SM +dogy's +doh's +doily/SM +doing/MU +doldrum/S +doldrums/M +dole/MGDS +doled/F +doleful/PY +dolefuller +dolefullest +dolefulness/MS +doles/F +doling/F +doll/MDGS +dollar/SM +dollop/GSMD +dolly/SDMG +dolmen/MS +dolomite/SM +dolomitic +dolor/SM +dolorous/Y +dolphin/SM +dolt/MS +doltish/YP +doltishness/SM +domain/MS +dome/DSMG +domestic/S +domestically +domesticate/DSXGN +domesticated/U +domestication/M +domesticity/MS +domicile/SDMG +domiciliary +dominance/MS +dominant/YS +dominate/VNGXSD +domination/M +dominator/M +dominatrices +dominatrix +domineer/DSG +domineering/YP +domineeringness/M +dominion/MS +domino/M +dominoes +don't +don/S +dona/MS +donate/XVGNSD +donation/M +donative/M +done/AUF +dong/GDMS +dongle/S +donkey/MS +donned +donning +donnish/YP +donnishness/M +donnybrook/MS +donor/MS +donut/MS +donutted +donutting +doodad/MS +doodle/SRDZG +doodlebug/MS +doodler/M +doohickey/MS +doom/MDGS +doomsday/SM +door/GDMS +doorbell/SM +doorhandles +doorkeep/RZ +doorkeeper/M +doorknob/SM +doorman/M +doormat/SM +doormen +doornail/M +doorplate/SM +doors/I +doorstep/MS +doorstepped +doorstepping +doorstop/MS +doorway/MS +dooryard/SM +dopa/SM +dopamine +dopant/M +dope/DRSMZG +doper/M +dopey +dopier +dopiest +dopiness/S +dork/S +dorky/RT +dorm/MRZS +dormancy/MS +dormant/S +dormer/M +dormice +dormitory/SM +dormouse/M +dorsal/YS +dory/SM +dos/GDS +dosage/SM +dose/M +dosimeter/MS +dosimetry/M +dossier/MS +dost +dot/MDRSJZG +dotage/SM +dotard/MS +dote/S +doter/M +doting/Y +dotted +dottiness/M +dotting +dotty/PRT +double/GPSRDZ +doubled/UA +doubleheader/MS +doubleness/M +doubler/M +doubles/M +doublespeak/S +doublet/MS +doublethink/M +doubleton/M +doubling/A +doubloon/MS +doubly +doubt/AGSDMB +doubted/U +doubter/SM +doubtful/YP +doubtfulness/SM +doubting/Y +doubtless/YP +doubtlessness/M +douche/GSDM +dough/M +doughs +doughty/RT +doughy/RT +dour/TYRP +dourness/MS +douse/SRDG +douser/M +dove/RSM +dovecote/MS +dovetail/GSDM +dovish +dowager/SM +dowdily +dowdiness/MS +dowdy/TPSR +dowel/GMDS +dower/GDMS +down/GZSRD +downbeat/SM +downcast/S +downdraft/M +downer/M +downfall/NMS +downgrade/GSD +downhearted/PY +downheartedness/MS +downhill/RS +downland +download/DGS +downpipes +downplay/GDS +downpour/MS +downrange +downright/YP +downrightness/M +downriver +downscale/GSD +downside/S +downsize/DSG +downslope +downspout/SM +downstage/S +downstairs +downstate/SR +downstream +downswing/MS +downtime/SM +downtown/MRS +downtowner/M +downtrend/M +downtrodden +downturn/MS +downward/YPS +downwardness/M +downwind +downy/RT +dowry/SM +dowse/GZSRD +dowser/M +doxology/MS +doyen/SM +doyenne/SM +doz/XGNDRS +doze +dozen/GHD +dozenths +dozer/M +dozy +dpt +drab/YSP +drabbed +drabber +drabbest +drabbing +drabness/MS +drachma/MS +draconian +draft/AMDGS +draftee/SM +drafter/MS +draftily +draftiness/SM +drafting/S +draftsman/M +draftsmanship/SM +draftsmen +draftsperson +draftswoman +draftswomen +drafty/PTR +drag/MS +dragged +dragger/M +dragging/Y +draggy/RT +dragnet/MS +dragon/SM +dragonfly/SM +dragonhead/M +dragoon/DMGS +drain/SZGRDM +drainage/MS +drainboard/SM +drained/U +drainer/M +drainpipe/MS +drake/SM +dram/MS +drama/SM +dramatic/S +dramatical/Y +dramatically/U +dramatics/M +dramatist/MS +dramatization/MS +dramatize/SRDZG +dramatized/U +dramatizer/M +dramaturgy/M +drammed +dramming +drank +drape/SRDGZ +draper/M +drapery/MS +drastic +drastically +drat/S +dratted +dratting +draw/ASG +drawable +drawback/MS +drawbridge/SM +drawer/SM +drawing/SM +drawl/RDSG +drawler/M +drawling/Y +drawly +drawn/AI +drawnly +drawnness +drawstring/MS +dray/SMDG +dread/SRDG +dreadful/YPS +dreadfulness/SM +dreadlocks +dreadnought/SM +dream/SMRDZG +dreamboat/SM +dreamed/U +dreamer/M +dreamily +dreaminess/SM +dreaming/Y +dreamland/SM +dreamless/PY +dreamlessness/M +dreamlike +dreamworld/S +dreamy/PTR +drear/S +drearily +dreariness/SM +dreary/TRSP +dredge/MZGSRD +dredger/M +dreg/MS +drench/GDRS +drencher/M +dress/ADRSG +dressage/MS +dressed/U +dresser's/A +dresser/MS +dresses/U +dressiness/SM +dressing/MS +dressmaker/MS +dressmaking/SM +dressy/PTR +drew/A +drib/SM +dribble/DRSGZ +dribbler/M +driblet/SM +dried/U +drier/M +drift/RDZSG +drifter/M +drifting/Y +driftwood/SM +drill/MRDZGS +driller/M +drilling/M +drillmaster/SM +drink/BRSZG +drinkable/S +drinker/M +drip/SM +dripped +dripping/MS +drippy/RT +drive/SRBGZJ +drivel/GZDRS +driveler/M +driven/P +driver/M +driveway/MS +drizzle/DSGM +drizzling/Y +drizzly/TR +drogue/MS +droll/RDSPTG +drollery/SM +drollness/MS +drolly +dromedary/MS +drone/SRDGM +droning/Y +drool/GSRD +droop/SGD +droopiness/MS +drooping/Y +droopy/PRT +drop/SM +drophead +dropkick/S +droplet/SM +dropout/MS +dropped +dropper/SM +dropping/MS +dropsical +dropsy/MS +drosophila/M +dross/SM +drought/SM +drove/SRDGZ +drover/M +drown/RDSJG +drowner/M +drowse/SDG +drowsily +drowsiness/SM +drowsy/PTR +drub/S +drubbed +drubber/MS +drubbing/SM +drudge/MGSRD +drudger/M +drudgery/SM +drudging/Y +drug/SM +drugged +druggie/SRT +drugging +druggist/SM +drugless +drugstore/SM +druid/MS +druidism/MS +drum/SM +drumbeat/SGM +drumhead/M +drumlin/MS +drummed +drummer/SM +drumming +drumstick/SM +drunk/SRNYMT +drunkard/SM +drunken/YP +drunkenness/SM +drupe/SM +druthers +dry/GYDRSTZ +dryad/MS +dryer/MS +dryish +dryness/SM +drys +drystone +drywall/GSD +dual/YS +dualism/MS +dualist/M +dualistic +duality/MS +dub/S +dubbed +dubber/S +dubbin/MS +dubbing/M +dubiety/MS +dubious/YP +dubiousness/SM +ducal +ducat/SM +duce's +duce/CAIKF +duchess/MS +duchy/SM +duck/GSRDM +duckbill/SM +ducker/M +duckling/SM +duckpins +duckpond +duckweed/MS +ducky/RSMT +duct's/A +duct/KMSF +ducted/CFI +ductile/I +ductility/SM +ducting/F +ductless +ducts/CI +ductwork/M +dud/GMDS +dudder +dude/MS +dudgeon/SM +due/PMS +duel/MRDGZSJ +duelist/MS +dueness/M +duenna/MS +duet/MS +duetted +duetting +duff/GZSRDM +duffel/M +duffer/M +dug/S +dugout/SM +duh +duke/DSMG +dukedom/SM +dulcet/SY +dulcify +dulcimer/MS +dull/SRDPGT +dullard/MS +dullness/MS +dully +dulness's +duly/U +dumb/PSGTYRD +dumbbell/MS +dumbfound/GSDR +dumbness/MS +dumbstruck +dumbwaiter/SM +dumdum/MS +dummy/SDMG +dump/SGZRD +dumper/UM +dumpiness/MS +dumpling/MS +dumpster/S +dumpy/PRST +dun/S +dunce/MS +dunderhead/MS +dune/SM +dung/SGDM +dungaree/SM +dungeon/GSMD +dunghill/MS +dunk/GSRD +dunker/M +dunned +dunner +dunnest +dunning +dunno/M +duo/MS +duodecimal/S +duodena +duodenal +duodenum/M +duologue/M +duopolist +duopoly/M +dupe/NGDRSMZ +duper/M +dupion/M +duple +duplex/MSRDG +duplexer/M +duplicability/M +duplicable +duplicate/ADSGNX +duplication/AM +duplicative +duplicator/MS +duplicitous +duplicity/SM +durability/MS +durable/PS +durableness/M +durably +durance/SM +duration/MS +durational +duress/SM +during +durst +durum/MS +dusk/GDMS +duskiness/MS +dusky/RPT +dust/MRDGZS +dustbin/MS +dustcart/M +dustcover +duster/M +dustily +dustiness/MS +dusting/M +dustless +dustman/M +dustmen +dustpan/SM +dusty/RPT +dutch/MS +duteous/Y +dutiable +dutiful/UPY +dutifulness/S +duty/SM +duvet/SM +duxes +dwarf/MTGSPRD +dwarfish +dwarfism/MS +dweeb/S +dwell/IGS +dweller/SM +dwelling/MS +dwelt/I +dwindle/GSD +dyad/MS +dyadic +dybbuk/SM +dybbukim +dye/JDRSMZG +dyed/A +dyeing/M +dyer/M +dyes/A +dyestuff/SM +dying/UA +dyke's +dynamic/S +dynamical/Y +dynamics/M +dynamism/SM +dynamite/RSDZMG +dynamiter/M +dynamized +dynamo/MS +dynastic +dynasty/MS +dyne/M +dysentery/SM +dysfunction/MS +dysfunctional +dyslectic/S +dyslexia/MS +dyslexic/S +dyslexically +dyspepsia/MS +dyspeptic/S +dysprosium/MS +dystopia/M +dystrophy/M +dz +débutante/SM +décolletage/S +décolleté +démodé +dérailleur/MS +déshabillé's +détente +e'en +e'er +e's +e/FMDS +ea +each +eager/TSPRYM +eagerness/MS +eagle/SDGM +eaglet/SM +ear/GSMDYH +earache/SM +eardrum/SM +earful/MS +earing/M +earl/MS +earldom/MS +earliness/SM +earlobe/S +early/PRST +earmark/DGSJ +earmuff/SM +earn/GRDZTSJ +earned/U +earner/M +earnest/PYS +earnestness/MS +earning/M +earphone/MS +earpieces +earplug/MS +earring/MS +earshot/MS +earsplitting +earth/MDNYG +earthbound +earthed/U +earthenware/MS +earthiness/SM +earthliness/M +earthling/MS +earthly/TPR +earthmen +earthmover/M +earthmoving +earthquake/SDGM +earths/U +earthshaking +earthward/S +earthwork/MS +earthworm/MS +earthy/PTR +earwax/MS +earwig/MS +earwigged +earwigging +ease's/EU +ease/LDRSMG +eased/E +easel/MS +easement/MS +easer/M +eases/UE +easies +easily/U +easiness/MSU +easing/M +east/GSMR +eastbound +easter/Y +easterly/S +eastern/ZR +easterner/M +easternmost +easting/M +eastward/S +easy/PUTR +easygoing/P +easygoingness/M +eat/SJZGNRB +eatable/U +eatables +eaten/U +eater/M +eatery/MS +eating/M +eave/SM +eavesdrop/S +eavesdropped +eavesdropper/MS +eavesdropping +ebb/DSG +ebony/SM +ebullience/SM +ebullient/Y +ebullition/SM +eccentric/MS +eccentrically +eccentricity/SM +eccl +ecclesiastic/MS +ecclesiastical/Y +echelon/SGDM +echinoderm/SM +echo/DMG +echoed/A +echoes/A +echoic +echolocation/SM +eclectic/S +eclectically +eclecticism/MS +eclipse/MGSD +ecliptic/MS +eclogue/MS +ecocide/SM +ecol +ecologic +ecological/Y +ecologist/MS +ecology/MS +econ +econometric/S +econometricians +econometrics/M +economic/S +economical/YU +economics/M +economist/MS +economization +economize/GZSRD +economizer/M +economizing/U +economy/MS +ecosystem/MS +ecru/SM +ecstasy/MS +ecstatic/S +ecstatically +ectoplasm/M +ecumenic/MS +ecumenical/Y +ecumenicism/SM +ecumenicist/MS +ecumenics/M +ecumenism/SM +ecumenist/MS +eczema/MS +ed/ASC +eddy/SDMG +edelweiss/MS +edema/SM +edematous +eden +edge/DRSMZGJ +edgeless +edger/M +edgewise +edgily +edginess/MS +edging/M +edgy/TRP +edibility/MS +edible/SP +edibleness/SM +edict/SM +edification/M +edifice/SM +edifier/M +edify/ZNXGRSD +edifying/U +edit/SADG +editable +edited/IU +edition/SM +editor/MS +editorial/YS +editorialist/M +editorialize/DRSG +editorializer/M +editorship/MS +eds +educ/DBG +educability/SM +educable/S +educate/XASDGN +educated/YP +education/AM +educational/Y +educationalists +educationists +educative +educator/MS +educe/S +eduction/M +edutainment/S +eek/S +eel/MS +eelgrass/M +eerie/RT +eerily +eeriness/MS +efface/SRDLG +effaceable/I +effacement/MS +effacer/M +effect/SMDGV +effective/YIP +effectiveness/ISM +effectives +effector/MS +effectual/IYP +effectualness/MI +effectuate/SDGN +effectuation/M +effeminacy/MS +effeminate/SY +effendi/MS +efferent/SY +effervesce/GSD +effervescence/SM +effervescent/Y +effete/YP +effeteness/SM +efficacious/IPY +efficaciousness/MI +efficacy/IMS +efficiency/MIS +efficient/ISY +effigy/SM +effloresce +efflorescence/SM +efflorescent +effluence/SM +effluent/MS +effluvia +effluvium/M +efflux/M +effluxion +effort/MS +effortless/PY +effortlessness/SM +effrontery/MS +effulgence/SM +effulgent +effuse/XSDVGN +effusion/M +effusive/YP +effusiveness/MS +egad +egalitarian/I +egalitarianism/MS +egalitarians +egg/GMDRS +eggbeater/SM +eggcup/MS +egger/M +egghead/SDM +eggheaded/P +eggnog/SM +eggplant/MS +eggshell/SM +egis's +eglantine/MS +ego/SM +egocentric/S +egocentrically +egocentricity/SM +egoism/SM +egoist/SM +egoistic +egoistical/Y +egomania/MS +egomaniac/MS +egotism/SM +egotist/MS +egotistic +egotistical/Y +egregious/PY +egregiousness/MS +egress/SDMG +egret/SM +eh +eider/SM +eiderdown/SM +eidetic +eigenfunction/MS +eigenstate/S +eigenvalue/SM +eigenvector/MS +eight/SM +eighteen/MHS +eighteenths +eightfold +eighth/MS +eighths +eightieths +eightpence +eighty/SHM +einsteinium/MS +eisteddfod/M +either +ejaculate/SDXNG +ejaculation/M +ejaculatory +eject/VGSD +ejecta +ejection/SM +ejector/SM +eke/DSG +eked/A +el/AS +elaborate/SDYPVNGX +elaborateness/SM +elaboration/M +elaborators +eland/SM +elans +elapse/SDG +elastic/S +elastically/I +elasticated +elasticity/SM +elasticize/GDS +elastodynamics +elastomer/M +elate/SRDXGN +elated/PY +elatedness/M +elater/M +elation/M +elbow/GDMS +elbowroom/SM +elder/SY +elderberry/MS +elderflower +elderliness/M +elderly/PS +eldest +elect/ASGD +electable/U +elected/U +election/SAM +electioneer/GSD +elective/SPY +electiveness/M +elector/SM +electoral/Y +electorate/SM +electress/M +electric/S +electrical/PY +electricalness/M +electrician/SM +electricity/SM +electrification/M +electrifier/M +electrify/ZXGNDRS +electro/M +electrocardiogram/MS +electrocardiograph/M +electrocardiographs +electrocardiography/MS +electrochemical/Y +electrocute/GNXSD +electrocution/M +electrode/SM +electrodynamic/YS +electrodynamics/M +electroencephalogram/SM +electroencephalograph/M +electroencephalographic +electroencephalographs +electroencephalography/MS +electrologist/MS +electroluminescent +electrolysis/M +electrolyte/SM +electrolytic +electrolytically +electrolyze/SDG +electromagnet/SM +electromagnetic +electromagnetically +electromagnetism/SM +electromechanical +electromechanics +electromotive +electromyograph +electromyographic +electromyographically +electromyography/M +electron/MS +electronegative +electronic/S +electronically +electronics/M +electrophoresis/M +electrophorus/M +electroplate/DSG +electroscope/MS +electroscopic +electroshock/GDMS +electrostatic/S +electrostatics/M +electrotherapist/M +electrotype/GSDZM +electroweak +eleemosynary +elegance/ISM +elegant/YI +elegiac/S +elegiacal +elegy/SM +elem +element/MS +elemental/YS +elementarily +elementariness/M +elementary/P +elephant/SM +elephantiases +elephantiasis/M +elephantine +elev/NX +elevate/XDSNG +elevated/S +elevation/M +elevator/SM +eleven/HM +elevens/S +elevenths +elf/M +elfin/S +elfish +elicit/GSD +elicitation/MS +elide/GSD +eligibility/ISM +eligible/SI +eliminate/XSDYVGN +elimination/M +eliminator/SM +elision/SM +elite/MPS +elitism/SM +elitist/SM +elixir/MS +elk/MS +ell/MS +ellipse/MS +ellipsis/M +ellipsoid/MS +ellipsoidal +ellipsometer/MS +ellipsometry +elliptic +elliptical/YS +ellipticity/M +elm/MRS +elocution/SM +elocutionary +elocutionist/MS +elodea/S +elongate/NGXSD +elongation/M +elope/SRDLG +elopement/MS +eloper/M +eloquence/SM +eloquent/IY +els +else/M +elsewhere +eluate/SM +elucidate/SDVNGX +elucidation/M +elude/GSD +elusive/YP +elusiveness/SM +elute/DGN +elution/M +elven +elver/SM +elves/M +elvish +elysian +em/M +emaciate/NGXDS +emaciation/M +emacs/M +email/SMDG +emanate/XSDVNG +emanation/M +emancipate/DSXGN +emancipation/M +emancipator/MS +emasculate/GNDSX +emasculation/M +embalm/ZGRDS +embalmer/M +embank/GLDS +embankment/MS +embarcadero +embargo/GMD +embargoes +embark/ADESG +embarkation/EMS +embarrass/SDLG +embarrassed/U +embarrassedly +embarrassing/Y +embarrassment/MS +embassy/MS +embattle/DSG +embed/S +embeddable +embedded +embedder +embedding/MS +embellish/LGRSD +embellished/U +embellisher/M +embellishment/MS +ember/MS +embezzle/LZGDRS +embezzlement/MS +embezzler/M +embitter/LGDS +embitterment/SM +emblazon/DLGS +emblazonment/SM +emblem/GSMD +emblematic +embodier/M +embodiment/ESM +embody/ESDGA +embolden/DSG +embolism/SM +embosom +emboss/ZGRSD +embosser/M +embouchure/SM +embower/GSD +embrace/RSDVG +embraceable +embracer/M +embracing/Y +embrasure/MS +embrittle +embrocation/SM +embroider/SGZDR +embroiderer/M +embroidery/MS +embroil/SLDG +embroilment/MS +embryo/SM +embryologist/SM +embryology/MS +embryonic +emcee/SDM +emceeing +emend/SRDGB +emendation/MS +emerald/SM +emerge/ADSG +emergence/MAS +emergency/SM +emergent/S +emerita +emeritae +emeriti +emeritus +emery/MGSD +emetic/S +emf/S +emigrant/MS +emigrate/SDXNG +emigration/M +eminence/MS +eminent/Y +emir/SM +emirate/SM +emissary/SM +emission/AMS +emissivity/MS +emit/S +emittance/M +emitted +emitter/SM +emitting +emollient/S +emolument/SM +emote/SDVGNX +emotion/M +emotional/UY +emotionalism/MS +emotionality/M +emotionalize/GDS +emotionless +emotive/Y +empaneled +empaneling +empath +empathetic +empathetical/Y +empathic +empathize/SDG +empathy/MS +emperor/MS +emphases +emphasis/M +emphasize/ZGCRSDA +emphatic/U +emphatically/U +emphysema/SM +emphysematous +empire/MS +empiric/SM +empirical/Y +empiricism/SM +empiricist/SM +emplace/L +emplacement/MS +employ/LAGDS +employability/UM +employable/US +employed/U +employee/SM +employer/SM +employment/UMAS +emporium/MS +empower/GLSD +empowerment/MS +empress/MS +emptier/M +emptily +emptiness/SM +empty/GRSDPT +empyrean/SM +ems/C +emu/SM +emulate/SDVGNX +emulation/M +emulative/Y +emulator/MS +emulsification/M +emulsifier/M +emulsify/NZSRDXG +emulsion/SM +en/BM +enable/SRDZG +enabler/M +enact/SGALD +enactment/ASM +enamel/ZGJMDRS +enameler/M +enamelware/SM +enamor/DSG +enc +encamp/LSDG +encampment/MS +encapsulate/SDGNX +encapsulation/M +encase/GSDL +encasement/SM +encephalitic +encephalitides +encephalitis/M +encephalographic +encephalopathy/M +enchain/SGD +enchant/ESLDG +enchanter/MS +enchanting/Y +enchantment/MSE +enchantress/MS +enchilada/SM +encipher/SRDG +encipherer/M +encircle/GLDS +encirclement/SM +encl +enclave/MGDS +enclose/GDS +enclosed/U +enclosure/SM +encode/ZJGSRD +encoder/M +encomium/SM +encompass/GDS +encore/GSD +encounter/GSD +encourage/SRDGL +encouragement/SM +encourager/M +encouraging/Y +encroach/LGRSD +encroacher/M +encroachment/MS +encrust/DSG +encrustation/MS +encrypt/DGS +encrypted/U +encryption/SM +encumber/SEDG +encumbered/U +encumbrance/SRM +encumbrancer/M +ency +encyclical/SM +encyclopaedia's +encyclopedia/SM +encyclopedic +encyst/GSLD +encystment/MS +end/ZGVMDRSJ +endanger/DGSL +endangerment/SM +endear/GSLD +endearing/Y +endearment/MS +endeavor/GZSMRD +endeavored/U +endeavorer/M +endemic/S +endemically +endemicity +ender/M +endgame/M +ending/M +endive/SM +endless/PY +endlessness/MS +endmost +endnote/MS +endocrine/S +endocrinologist/SM +endocrinology/SM +endogamous +endogamy/M +endogenous/Y +endomorphism/SM +endorse/DRSZGL +endorsement/MS +endorser/M +endoscope/MS +endoscopic +endoscopy/SM +endosperm/M +endothelial +endothermic +endow/GSDL +endowment/SM +endpoint/MS +endue/SDG +endungeoned +endurable/U +endurably/U +endurance/SM +endure/BSDG +enduring/YP +enduringness/M +endways +enema/SM +enemy/SM +energetic/S +energetically +energetics/M +energize/ZGDRS +energized/U +energizer/M +energy/MS +enervate/XNGVDS +enervation/M +enfeeble/GLDS +enfeeblement/SM +enfilade/MGDS +enfold/SGD +enforce/LDRSZG +enforceability/M +enforceable/U +enforced/Y +enforcement/SM +enforcer/M +enforcible/U +enfranchise/ELDRSG +enfranchisement/EMS +enfranchiser/M +engage/ADSGE +engagement/SEM +engaging/Y +engender/DGS +engine/MGSD +engineer/GSMDJ +engineering/MY +england/ZR +engorge/LGDS +engorgement/MS +engram/MS +engrave/ZGDRSJ +engraver/M +engraving/M +engross/GLDRS +engrossed/Y +engrosser/M +engrossing/Y +engrossment/SM +engulf/GDSL +engulfment/SM +enhance/LZGDRS +enhanceable +enhancement/MS +enhancer/M +enharmonic +enigma/MS +enigmatic +enigmatically +enjambement's +enjambment/MS +enjoin/GSD +enjoinder +enjoy/GBDSL +enjoyability +enjoyable/P +enjoyableness/M +enjoyably +enjoyment/SM +enlarge/LDRSZG +enlargeable +enlargement/MS +enlarger/M +enlighten/GDSL +enlightened/U +enlightening/U +enlightenment/SM +enlist/SAGDL +enlistee/MS +enlister/M +enlistment/SAM +enliven/LDGS +enlivenment/SM +enmesh/DSLG +enmeshment/SM +enmity/MS +ennoble/LDRSG +ennoblement/SM +ennobler/M +ennui/SM +enormity/SM +enormous/YP +enormousness/MS +enough +enoughs +enplane/DSG +enqueue/DS +enquirer/S +enquiringly +enrage/SDG +enrapture/GSD +enrich/LDSRG +enricher/M +enrichment/SM +enrobed +enroll/LGSD +enrollee/SM +enrollment/SM +ens +ensconce/DSG +ensemble/MS +enshrine/DSLG +enshrinement/SM +enshroud/DGS +ensign/SM +ensilage/DSMG +enslave/ZGLDSR +enslavement/MS +enslaver/M +ensnare/GLDS +ensnarement/SM +ensue/SDG +ensure/SRDZG +ensurer/M +entail/SDRLG +entailer/M +entailment/MS +entangle/EGDRSL +entanglement/ESM +entangler/EM +entente/MS +enter/ASDG +entered/U +enterer/M +enteritides +enteritis/SM +enterprise/GMSR +enterpriser/M +enterprising/Y +entertain/SGZRDL +entertainer/M +entertaining/Y +entertainment/SM +enthalpy/SM +enthrall/GDSL +enthrallment/SM +enthrone/GDSL +enthronement/MS +enthuse/DSG +enthusiasm/SM +enthusiast/MS +enthusiastic/U +enthusiastically/U +entice/SRDJLZG +enticement/SM +enticing/Y +entire/SY +entirety/SM +entitle/GLDS +entitlement/MS +entity/SM +entomb/GDSL +entombment/MS +entomological +entomologist/S +entomology/MS +entourage/SM +entr'acte/S +entrails +entrain/GSLDR +entrainer/M +entrance/MGDSL +entrancement/MS +entranceway/M +entrancing/Y +entrant/MS +entrap/SL +entrapment/SM +entrapped +entrapping +entreat/SGD +entreating/Y +entreaty/SM +entrench/LSDG +entrenchment/MS +entrepreneur/MS +entrepreneurial +entrepreneurship/M +entropic +entropy/MS +entrust/DSG +entry/ASM +entryway/SM +entrée/S +entwine/DSG +enumerable +enumerate/AN +enumerated/U +enumerates +enumerating +enumeration's/A +enumeration/SM +enumerative +enumerator/SM +enunciable +enunciate/XGNSD +enunciated/U +enunciation/M +enureses +enuresis/M +envelop/ZGLSDR +envelope/MS +enveloper/M +envelopment/MS +envenom/SDG +enviable/U +enviableness/M +enviably +envied/U +envier/M +envious/PY +enviousness/SM +environ/LGSD +environment/MS +environmental/Y +environmentalism/SM +environmentalist/SM +envisage/DSG +envision/GSD +envoy/SM +envy/SRDMG +envying/Y +enzymatic +enzymatically +enzyme/SM +enzymology/M +eohippus/M +eolian +eon/SM +epaulet/SM +ephedrine/MS +ephemera/MS +ephemeral/SY +ephemerids +ephemeris/M +epic/SM +epically +epicenter/SM +epicure/SM +epicurean/S +epicycle/MS +epicyclic +epicyclical/Y +epicycloid/M +epidemic/MS +epidemically +epidemiological/Y +epidemiologist/MS +epidemiology/MS +epidermal +epidermic +epidermis/MS +epidural +epigenetic +epiglottis/SM +epigram/MS +epigrammatic +epigraph/RM +epigrapher/M +epigraphs +epigraphy/MS +epilepsy/SM +epileptic/S +epilogue/SDMG +epinephrine/SM +epiphany/SM +epiphenomena +episcopacy/MS +episcopal/Y +episcopalian +episcopate/MS +episode/SM +episodic +episodically +epistemic +epistemological/Y +epistemology/M +epistle/MRS +epistolary/S +epistolatory +epitaph/GMD +epitaphs +epitaxial/Y +epitaxy/M +epithelial +epithelium/MS +epithet/MS +epitome/MS +epitomize/SRDZG +epitomized/U +epitomizer/M +epoch/M +epochal/Y +epochs +eponymous +epoxy/GSD +epsilon/SM +equability/MS +equable/P +equableness/M +equably +equal/USDY +equaling +equality/ISM +equalization/MS +equalize/DRSGJZ +equalized/U +equalizer/M +equalizes/U +equanimity/MS +equate/NGXBSD +equation/M +equator/SM +equatorial/S +equerry/MS +equestrian/S +equestrianism/SM +equestrienne/SM +equiangular +equidistant/Y +equilateral/S +equilibrate/GNSD +equilibration/M +equilibrium/MSE +equine/S +equinoctial/S +equinox/MS +equip/AS +equipage/SM +equipartition/M +equipment/SM +equipoise/GMSD +equipotent +equipped/AU +equipping/A +equiproportional +equiproportionality +equiproportionate +equitable/I +equitableness/M +equitably/I +equitation/SM +equity/IMS +equiv +equivalence/DSMG +equivalent/SY +equivocal/UY +equivocalness/MS +equivocate/NGSDX +equivocation/M +equivocator/SM +era/MS +eradicable/I +eradicate/SDXVGN +eradication/M +eradicator/SM +eras/SRDBGZ +erase/N +eraser/M +erasion/M +erasure/MS +erbium/SM +ere +erect/GPSRDY +erectile +erection/SM +erectness/MS +erector/SM +erelong +eremite/MS +erg/SM +ergo +ergodic +ergodicity/M +ergonomic/U +ergonomically +ergonomics/M +ergophobia +ergosterol/SM +ergot/SM +eris +ermine/MSD +erode/SDG +erodible +erogenous +erosible +erosion/SM +erosional +erosive/P +erosiveness/M +erotic/S +erotica/M +erotically +eroticism/MS +err/DGS +errancy/MS +errand/MS +errant/YS +errantry/M +errata/SM +erratic/S +erratically +erratum/MS +erring/UY +erroneous/YP +erroneousness/M +error/SM +ersatz/S +erst +erstwhile +eruct/DGS +eructation/MS +erudite/NYX +erudition/M +erupt/DSVG +eruption/SM +eruptive/SY +erysipelas/SM +erythrocyte/SM +es +escadrille/M +escalate/CDSXGN +escalation/MC +escalator/SM +escallop/SGDM +escapable/I +escapade/SM +escape/LGSRDB +escapee/MS +escapement/MS +escaper/M +escapism/SM +escapist/S +escapology +escarole/MS +escarpment/MS +eschatology/M +eschew/SGD +escort/SGMD +escritoire/SM +escrow/DMGS +escudo/MS +escutcheon/SM +esophageal +esophagi +esophagus/M +esoteric +esoterica +esoterically +esp +espadrille/MS +espalier/SMDG +especial/Y +espionage/SM +esplanade/SM +espousal/MS +espouse/SRDG +espouser/M +espresso/SM +esprit/SM +espy/GSD +esquire/GMSD +essay/SZMGRD +essayer/M +essayist/SM +essence/MS +essential/USI +essentialist/M +essentially +essentialness/M +est/RZ +establish/LAEGSD +established/U +establisher/M +establishment/EMAS +estate/GSDM +esteem/EGDS +ester/M +esthete's +esthetic's +esthetically +esthetics's +estimable/I +estimableness/M +estimate/XDSNGV +estimating/A +estimation/M +estimator/SM +estoppal +estrange/DRSLG +estrangement/SM +estranger/M +estrogen/SM +estrous +estrus/SM +estuarine +estuary/SM +et +eta/SM +etc +etcetera/SM +etch/GZJSRD +etcher/M +etching/M +eternal/PSY +eternalness/SM +eternity/SM +ethane/SM +ethanol/MS +ether/SM +ethereal/PY +etherealness/M +etherized +ethic/MS +ethical/PYS +ethically/U +ethicalness/M +ethicist/S +ethnic/S +ethnically +ethnicity/MS +ethnocentric +ethnocentrism/MS +ethnographers +ethnographic +ethnography/M +ethnological +ethnologist/SM +ethnology/SM +ethnomethodology +ethological +ethologist/MS +ethology/SM +ethos/SM +ethyl/SM +ethylene/MS +etiologic +etiological +etiology/SM +etiquette/SM +etymological/Y +etymologist/SM +etymology/MS +eucalypti +eucalyptus/SM +euchre/MGSD +euclidean +eugenic/S +eugenically +eugenicist/SM +eugenics/M +eulogist/MS +eulogistic +eulogize/GRSDZ +eulogized/U +eulogizer/M +eulogy/MS +eunuch/M +eunuchs +euphemism/MS +euphemist/M +euphemistic +euphemistically +euphonious/Y +euphonium/M +euphony/SM +euphoria/SM +euphoric +euphorically +eureka/S +europium/MS +eutectic +euthanasia/SM +euthenics/M +evacuate/DSXNGV +evacuation/M +evacuee/MS +evade/SRDBGZ +evader/M +evaluable +evaluate/ADSGNX +evaluated/U +evaluation/MA +evaluational +evaluative +evaluator/MS +evanescence/MS +evanescent +evangelic +evangelical/YS +evangelicalism/SM +evangelism/SM +evangelist/MS +evangelistic +evangelize/GDS +evaporate/VNGSDX +evaporation/M +evaporative/Y +evaporator/MS +evasion/SM +evasive/PY +evasiveness/SM +eve's/A +eve/RSM +even/PUYRT +evened +evener/M +evenhanded/YP +evening/SM +evenness/MSU +evens +evensong/MS +event/SGM +eventful/YU +eventfulness/SM +eventide/SM +eventual/Y +eventuality/MS +eventuate/GSD +ever/T +everglade/MS +evergreen/S +everlasting/PYS +everlastingness/M +everliving +evermore +every +everybody/M +everyday/P +everydayness/M +everyman +everyone/MS +everyplace +everything +everywhere +eves/A +evict/DGS +eviction/SM +evidence/MGSD +evident/YS +evidential/Y +evil/YRPTS +evildoer/SM +evildoing/MS +evilness/MS +evince/SDG +eviscerate/GNXDS +evisceration/M +evocable +evocate/NVX +evocation/M +evocative/YP +evocativeness/M +evoke/SDG +evolute/NMXS +evolution/M +evolutionarily +evolutionary +evolutionist/MS +evolve/SDG +ewe/MZRS +ewer/M +ex/S +exacerbate/NGXDS +exacerbation/M +exact/TGSPRDY +exacter/M +exacting/YP +exactingness/M +exaction/SM +exactitude/ISM +exactly/I +exactness/MSI +exaggerate/DSXNGV +exaggerated/YP +exaggeration/M +exaggerative/Y +exaggerator/MS +exalt/ZRDGS +exaltation/SM +exalted/Y +exalter/M +exam/MNS +examen/M +examination's +examination/AS +examine/BGZDRS +examined/AU +examinees +examiner/M +examines/A +examining/A +example/DSGM +exampled/U +exasperate/DSXGN +exasperated/Y +exasperating/Y +exasperation/M +excavate/NGDSX +excavation/M +excavator/SM +exceed/SGDR +exceeder/M +exceeding/Y +excel/S +excelled +excellence/SM +excellency/MS +excellent/Y +excelling +excelsior/S +except/DSGV +exception/BMS +exceptionable/U +exceptional/YU +exceptionalness/M +excerpt/GMDRS +excerpter/M +excess/GVDSM +excessive/PY +excessiveness/M +exchange/GDRSZ +exchangeable +exchanger/M +exchequer/SM +excise/XMSDNGB +excision/M +excitability/MS +excitable/P +excitableness/M +excitably +excitation/SM +excitatory +excite/RSDLBZG +excited/Y +excitement/MS +exciter/M +exciting/U +excitingly +exciton/M +exclaim/SZDRG +exclaimer/M +exclamation/MS +exclamatory +exclude/DRSG +excluder/M +exclusion/SZMR +exclusionary +exclusioner/M +exclusive/SPY +exclusiveness/SM +exclusivity/MS +excommunicate/XVNGSD +excommunication/M +excoriate/GNXSD +excoriation/M +excrement/SM +excremental +excrescence/MS +excrescent +excreta +excrete/NGDRSX +excreter/M +excretion/M +excretory/S +excruciate/NGDS +excruciating/Y +excruciation/M +exculpate/XSDGN +exculpation/M +exculpatory +excursion/MS +excursionist/SM +excursive/PY +excursiveness/SM +excursus/MS +excusable/IP +excusableness/IM +excusably/I +excuse/BGRSD +excused/U +excuser/M +exec/MS +execrable/P +execrableness/M +execrably +execrate/DSXNGV +execration/M +executable/MS +execute/NGVZBXDRS +executer/M +execution/ZMR +executional +executioner/M +executive/SM +executor/SM +executrices +executrix/M +exegeses +exegesis/M +exegete/M +exegetic/S +exegetical +exemplar/MS +exemplariness/M +exemplary/P +exemplification/M +exemplifier/M +exemplify/ZXNSRDG +exempt/SDG +exemption/MS +exercise/ZDRSGB +exerciser/M +exert/SGD +exertion/MS +exeunt +exhalation/SM +exhale/GSD +exhaust/VGRDS +exhausted/Y +exhauster/M +exhaustible/I +exhausting/Y +exhaustion/SM +exhaustive/YP +exhaustiveness/MS +exhibit/VGSD +exhibition/ZMRS +exhibitioner/M +exhibitionism/MS +exhibitionist/MS +exhibitor/SM +exhilarate/XSDVNG +exhilarating/Y +exhilaration/M +exhort/DRSG +exhortation/SM +exhorter/M +exhumation/SM +exhume/GRSD +exhumer/M +exigence/S +exigency/SM +exigent/SY +exiguity/SM +exiguous +exile/SDGM +exist/SDG +existence/MS +existent/I +existential/Y +existentialism/MS +existentialist/MS +existentialistic +existents +exit/MDSG +exobiology/MS +exocrine +exodus/SM +exogamous +exogamy/M +exogenous/Y +exonerate/SDVGNX +exoneration/M +exorbitance/MS +exorbitant/Y +exorcise/SDG +exorcism/SM +exorcist/SM +exorcizer/M +exoskeleton/MS +exosphere/SM +exothermic +exothermically +exotic/PS +exotica +exotically +exoticism/SM +exoticness/M +exp +expand/DRSGZB +expandability/M +expanded/U +expander/M +expanse/DSXGNVM +expansible +expansion/M +expansionary +expansionism/MS +expansionist/MS +expansive/YP +expansiveness/S +expatiate/XSDNG +expatiation/M +expatriate/SDNGX +expatriation/M +expect/SBGD +expectancy/MS +expectant/YS +expectation/MS +expectational +expected/UPY +expecting/Y +expectorant/S +expectorate/NGXDS +expectoration/M +expedience/IS +expediency/IMS +expedient/YI +expedients +expedite/ZDRSNGX +expediter/M +expedition/M +expeditionary +expeditious/YP +expeditiousness/MS +expeditor's +expel/S +expellable +expelled +expelling +expend/SDRGB +expendable/S +expended/U +expender/M +expenditure/SM +expense/DSGVM +expensive/IYP +expensiveness/SMI +experience/ISDM +experienced/U +experiencing +experiential/Y +experiment/GSMDRZ +experimental/Y +experimentalism/M +experimentalist/SM +experimentation/SM +experimenter/M +expert's +expert/PISY +experted +experting +expertise/SM +expertize/GD +expertness/IM +expertnesses +expiable/I +expiate/XGNDS +expiation/M +expiatory +expiration/MS +expire/SDG +expired/U +expiry/MS +explain/ADSG +explainable/UI +explained/U +explainer/SM +explanation/MS +explanatory +expletive/SM +explicable/I +explicate/VGNSDX +explication/M +explicative/Y +explicit/PSY +explicitness/SM +explode/DSRGZ +exploded/U +exploder/M +exploit/ZGVSMDRB +exploitation/MS +exploitative +exploited/U +exploiter/M +exploration/MS +exploratory +explore/DSRBGZ +explored/U +explorer/M +explosion/MS +explosive/YPS +explosiveness/SM +expo/MS +exponent/MS +exponential/SY +exponentiate/XSDNG +exponentiation/M +export's +export/AGSD +exportability +exportable +exportation/SM +exporter/MS +expos/RSDZG +expose +exposed/U +exposer/M +exposit/D +exposition/SM +expositor/MS +expository +expostulate/DSXNG +expostulation/M +exposure/SM +expound/ZGSDR +expounder/M +express/GVDRSY +expressed/U +expresser/M +expressibility/I +expressible/I +expressibly/I +expression/MS +expressionism/SM +expressionist/S +expressionistic +expressionless/YP +expressive/IYP +expressiveness's/I +expressiveness/MS +expressway/SM +expropriate/XDSGN +expropriation/M +expropriator/SM +expulsion/MS +expunge/GDSR +expunger/M +expurgate/SDGNX +expurgated/U +expurgation/M +exquisite/YPS +exquisiteness/SM +ext +extant +extemporaneous/YP +extemporaneousness/MS +extempore/S +extemporization/SM +extemporize/ZGSRD +extemporizer/M +extend/SGZDR +extendability/M +extended/U +extendedly +extendedness/M +extender/M +extendibility/M +extendibles +extensibility/M +extensible/I +extension/SM +extensional/Y +extensive/PY +extensiveness/SM +extensor/MS +extent/SM +extenuate/XSDGN +extenuation/M +exterior/MYS +exterminate/XNGDS +extermination/M +exterminator/SM +extern/M +external/YS +externalities +externalization/SM +externalize/GDS +extinct/DGVS +extinction/MS +extinguish/BZGDRS +extinguishable/I +extinguisher/M +extirpate/XSDVNG +extirpation/M +extol/S +extolled +extoller/M +extolling +extort/DRSGV +extorter/M +extortion/ZSRM +extortionate/Y +extortioner/M +extortionist/SM +extra/S +extracellular/Y +extract/GVSBD +extraction/SM +extractive/Y +extractor/SM +extracurricular/S +extradite/XNGSDB +extradition/M +extragalactic +extralegal/Y +extramarital +extramural +extraneous/YP +extraneousness/M +extraordinarily +extraordinariness/M +extraordinary/PS +extrapolate/XVGNSD +extrapolation/M +extrasensory +extraterrestrial/S +extraterritorial +extraterritoriality/MS +extravagance/MS +extravagant/Y +extravaganza/SM +extravehicular +extravert's +extrema +extremal +extreme/DSRYTP +extremeness/MS +extremism/SM +extremist/MS +extremity/SM +extricable/I +extricate/XSDNG +extrication/M +extrinsic +extrinsically +extroversion/SM +extrovert/GMDS +extrude/GDSR +extruder/M +extrusion/MS +extrusive +exuberance/MS +exuberant/Y +exudate/XNM +exudation/M +exude/GSD +exult/DGS +exultant/Y +exultation/SM +exulting/Y +exurb/MS +exurban +exurbanite/SM +exurbia/MS +eye/GDRSMZ +eyeball/GSMD +eyebrow/MS +eyed/P +eyedropper/MS +eyeful/MS +eyeglass/MS +eyelash/MS +eyeless +eyelet/GSMD +eyelid/SM +eyeliner/MS +eyeopener/MS +eyeopening +eyepiece/SM +eyer/M +eyeshadow +eyesight/MS +eyesore/SM +eyestrain/MS +eyeteeth +eyetooth/M +eyewash/MS +eyewitness/SM +eyrie's +f's/KA +f/IRAC +fa/M +fable/GMSRD +fabler/M +fabric/MS +fabricate/SDXNG +fabrication/M +fabricator/MS +fabulists +fabulous/YP +fabulousness/M +facade/GMSD +face's +face/AGCSD +facecloth +facecloths +faceless/P +faceplate/M +facer/CM +facet/SGMD +facetious/YP +facetiousness/MS +facial/YS +facile/YP +facileness/M +facilitate/VNGXSD +facilitation/M +facilitator/SM +facilitatory +facility/MS +facing/MS +facsimile/MSD +facsimileing +fact/MS +faction/SM +factional +factionalism/SM +factious/PY +factiousness/M +factitious +facto +factoid/S +factor/SDMJG +factorial/MS +factoring's +factoring/A +factorisable +factorization/SM +factorize/GSD +factory/MS +factotum/MS +factual/PY +factuality/M +factualness/M +faculty/MS +fad/ZGSMDR +faddish +faddist/SM +fade/S +faded/U +fadedly +fadeout +fader/M +fading's +fading/U +faerie/MS +faery's +fag/MS +fagged +fagging +faggoting's +fagot/MDSJG +fagoting/M +fail/JSGD +failing's +failing/UY +faille/MS +failsafe +failure/SM +fain/GTSRD +faint/YRDSGPT +fainter/M +fainthearted +faintness/MS +fair/TURYP +faired +fairgoer/S +fairground/MS +fairing/MS +fairish +fairless +fairness's +fairness/US +fairs +fairway/MS +fairy/MS +fairyland/MS +fairytale +faith's +faith/U +faithed +faithful/UYP +faithfulness/MSU +faithfuls +faithing +faithless/YP +faithlessness/SM +faiths +fajitas +fake/ZGDRS +faker/M +fakir/SM +falafel +falcon/ZSRM +falconer/M +falconry/MS +fall/SGZMRN +fallacious/PY +fallaciousness/M +fallacy/MS +faller/M +fallibility/MSI +fallible/I +fallibleness/MS +fallibly/I +falloff/S +fallopian +fallout/MS +fallow/PSGD +fallowness/M +false/PTYR +falsehood/SM +falseness/SM +falsetto/SM +falsie/MS +falsifiability/M +falsifiable/U +falsification/M +falsifier/M +falsify/ZRSDNXG +falsity/MS +falter/RDSGJ +falterer/M +faltering/UY +fame/DSMG +famed/C +fames/C +familial +familiar/YPS +familiarity/MUS +familiarization/MS +familiarize/ZGRSD +familiarized/U +familiarizer/M +familiarizing/Y +familiarly/U +familiarness/M +family/MS +famine/SM +faming/C +famish/GSD +famous/PY +famously/I +famousness/M +fan/SM +fanatic/SM +fanatical/YP +fanaticalness/M +fanaticism/MS +fancied +fancier/SM +fanciest +fanciful/YP +fancifulness/MS +fancily +fanciness/SM +fancy/IS +fancying +fancywork/SM +fandango/SM +fanfare/SM +fanfold/M +fang/DMS +fangled +fanlight/SM +fanned +fanning +fanny/SM +fanout +fantail/SM +fantasia/SM +fantasist/M +fantasize/SRDG +fantastic/S +fantastical/Y +fantasy/GMSD +fanzine/S +far/GDR +farad/SM +faraway +farce/SDGM +farcical/Y +fare/MS +farer/M +farewell/DGMS +farfetchedness/M +farina/MS +farinaceous +farm/MRDGZSJ +farmer/M +farmhand/S +farmhouse/SM +farming/M +farmland/SM +farmstead/SM +farmworker/S +farmyard/MS +faro/MS +farrago/M +farragoes +farrier/SM +farrow/DMGS +farseeing +farsighted/YP +farsightedness/SM +fart/MDGS! +farther +farthermost +farthest +farthing/SM +fas +fascia/SM +fascicle/DSM +fasciculate/DNX +fasciculation/M +fascinate/SDNGX +fascinating/Y +fascination/M +fascism/MS +fascist/SM +fascistic +fashion's +fashion/ADSG +fashionable/PS +fashionableness/M +fashionably/U +fashioner/SM +fast/GTXSPRND +fastback/MS +fastball/S +fasten/AGUDS +fastener/MS +fastening/SM +fastidious/PY +fastidiousness/MS +fastness/MS +fat/PSGMDY +fatal/SY +fatalism/MS +fatalist/MS +fatalistic +fatalistically +fatality/MS +fatback/SM +fate/MS +fateful/YP +fatefulness/MS +fathead/SMD +fatheaded/P +father/DYMGS +fathered/U +fatherhood/MS +fatherland/SM +fatherless +fatherliness/M +fatherly/P +fathom/MDSBG +fathomable/U +fathomless +fatigue/MGSD +fatigued/U +fatiguing/Y +fatness/SM +fatso/M +fatted +fatten/JZGSRD +fattener/M +fatter +fattest/M +fattiness/SM +fatting +fatty/RSPT +fatuity/MS +fatuous/YP +fatuousness/SM +fatwa/SM +faucet/SM +fault/CGSMD +faultfinder/MS +faultfinding/MS +faultily +faultiness/MS +faultless/PY +faultlessness/SM +faulty/RTP +faun/MS +fauna/MS +fauvism/S +favor/ESMRDGZ +favorable/UMPS +favorableness/MU +favorably/U +favored's/U +favored/YPSM +favoredness/M +favorer/EM +favoring/MYS +favorings/U +favorite/SMU +favoritism/MS +favors/A +fawn/GZRDMS +fawner/M +fawning/Y +fax/GMDS +fay/MDRGS +faze/DSG +faïence/S +fealty/MS +fear/RDMSG +fearful/YP +fearfuller +fearfullest +fearfulness/MS +fearless/PY +fearlessness/MS +fearsome/PY +fearsomeness/M +feasibility/SM +feasible/UI +feasibleness/M +feasibly/U +feast/GSMRD +feaster/M +feat/MYRGTS +feater/C +feather/ZMDRGS +featherbed +featherbedding/SM +featherbrain/MD +feathered/U +feathering/M +featherless +featherlight +feathertop +featherweight/SM +feathery/TR +feats/C +feature/MGSD +featureless +febrile +fecal +feces +feckless/PY +fecklessness/M +fecund/I +fecundability +fecundate/XSDGN +fecundation/M +fecundity/SM +fed/U +federal/YS +federalism/SM +federalist/MS +federalization/MS +federalize/GSD +federate/FSDXVNG +federated/U +federation/FM +federative/Y +fedora/SM +feds +fee/MDS +feeble/TPR +feebleness/SM +feebly +feed/GRZJS +feedback/SM +feedbag/MS +feeder/M +feeding/M +feedlot/SM +feedstock +feedstuffs +feeing +feel/GZJRS +feeler/M +feeling/MYP +feelingly/U +feelingness/M +feet/M +feign/RDGS +feigned/U +feigner/M +feint/MDSG +feisty/RT +feldspar/MS +felicitate/XGNSD +felicitation/M +felicitous/IY +felicitousness/M +felicity/IMS +feline/SY +fell/PSGZTRD +fella/S +fellatio/SM +felled/A +feller/M +felling/A +fellness/M +fellow/SGDYM +fellowman +fellowmen +fellowship/SM +fellowshipped +fellowshipping +felon/MS +felonious/PY +feloniousness/M +felony/MS +felt/GSD +felting/M +fem/S +female/MPS +femaleness/SM +feminine/PYS +feminineness/M +femininity/MS +feminism/MS +feminist/MS +femme/MS +femoral +femur/MS +fen/MS +fence/SRDJGMZ +fenced/U +fencepost/M +fencer/M +fencing/M +fend/RDSCZG +fender/CM +fenestration/CSM +fenland/M +fennel/SM +fer/FLC +feral +ferment/FSCM +fermentation/MS +fermented +fermenter +fermenting +fermion/MS +fermium/MS +fern/MS +fernery/M +ferny/TR +ferocious/YP +ferociousness/MS +ferocity/MS +ferret/SMRDG +ferreter/M +ferric +ferris +ferrite/M +ferro +ferroelectric +ferromagnet/M +ferromagnetic +ferrous +ferrule/MGSD +ferry/SDMG +ferryboat/MS +ferryman/M +ferrymen +fertile/YP +fertileness/M +fertility/IMS +fertilization/ASM +fertilize/SRDZG +fertilized/U +fertilizer/M +fertilizes/A +ferule/SDGM +fervency/MS +fervent/Y +fervid/YP +fervidness/M +fervor/MS +fess's +fess/KGFSD +fest/RVZ +festal/S +fester/GD +festival/SM +festive/PY +festiveness/SM +festivity/SM +festoon/SMDG +feta/MS +fetal +fetch/RSDGZ +fetcher/M +fetching/Y +feted +fetich's +fetid/YP +fetidness/SM +feting +fetish/MS +fetishism/SM +fetishist/SM +fetishistic +fetlock/MS +fetter's +fetter/UGSD +fettle/GSD +fettling/M +fettuccine/S +fetus/SM +feud/MDSG +feudal/Y +feudalism/MS +feudalistic +feudatory/M +fever/SDMG +feverish/PY +feverishness/SM +few/PTRS +fewness/MS +fey/RT +fez/M +fezzes +ff +fiancé/MS +fiancée/S +fiasco/M +fiascoes +fiat/MS +fib/SZMR +fibbed +fibber/MS +fibbing +fiber/DM +fiberboard/MS +fiberfill/S +fiberglass/DSMG +fibril/MS +fibrillate/XGNDS +fibrillation/M +fibrin/MS +fibroblast/MS +fibroid/S +fibroses +fibrosis/M +fibrous/YP +fibrousness/M +fibula/M +fibulae +fibular +fices +fiche/SM +fichu/SM +fickle/RTP +fickleness/MS +ficos +fiction/SM +fictional/Y +fictionalization/MS +fictionalize/DSG +fictitious/PY +fictitiousness/M +fictive/Y +ficus +fiddle/GMZJRSD +fiddler/M +fiddlestick/SM +fiddly +fide/F +fidelity/IMS +fidget/DSG +fidgety +fiducial/Y +fiduciary/MS +fie/S +fief/MS +fiefdom/S +field/ZISMR +fielded +fielder/IM +fielding +fieldstone/M +fieldwork/ZMRS +fieldworker/M +fiend/MS +fiendish/YP +fiendishness/M +fierce/RPTY +fierceness/SM +fierily +fieriness/MS +fiery/PTR +fies/C +fiesta/MS +fife/DRSMZG +fifer/M +fifteen/HRMS +fifteenths +fifth/Y +fifths +fiftieths +fifty/HSM +fig/MLS +figged +figging +fight/ZSJRG +fightback +fighter/MIS +fighting/IS +figment/MS +figural +figuration/FSM +figurative/YP +figurativeness/M +figure's +figure/GFESD +figurehead/SM +figurer/SM +figurine/SM +figuring/S +filament/MS +filamentary +filamentous +filbert/MS +filch/SDG +file/KDRSGMZ +filed/AC +filename/SM +filer/KMCS +files/AC +filet's +filial/UY +filibuster/MDRSZG +filibusterer/M +filigree/MSD +filigreeing +filing/AC +filings +fill/BAJGSD +filled/U +filler/MS +fillet/MDSG +filleting/M +filling/M +fillip/MDGS +filly/SM +film/SGMD +filmdom/M +filminess/SM +filming/M +filmmaker/S +filmstrip/SM +filmy/RTP +filter/RDMSZGB +filtered/U +filterer/M +filth/M +filthily +filthiness/SM +filths +filthy/TRSDGP +filtrate/SDXMNG +filtrated/I +filtrates/I +filtrating/I +filtration/IMS +fin/TGMDRS +finagle/RSDZG +finagler/M +final/SY +finale/MS +finalist/MS +finality/MS +finalization/SM +finalize/GSD +finance/MGSDJ +financed/A +finances/A +financial/Y +financier/DMGS +financing/A +finch/MS +find/BRJSGZ +findable/U +finder/M +finding/M +fine's +fine/FGSCRDA +finely +fineness/MS +finery/MAS +finespun +finesse/SDMG +finger/SGRDMJ +fingerboard/SM +fingerer/M +fingering/M +fingerless +fingerling/M +fingernail/MS +fingerprint/SGDM +fingertip/MS +finial/SM +finical +finickiness/S +finicky/RPT +fining/M +finis/SM +finish/JZGRSD +finished/UA +finisher/M +finishes/A +finite/ISPY +finitely/C +finiteness/MIC +fink/GDMS +finned +finner +finning +finny/RT +fiord's +fir/ZGJMDRHS +fire/MS +firearm/SM +fireball/SM +fireboat/M +firebomb/MDSG +firebox/MS +firebrand/MS +firebreak/SM +firebrick/SM +firebug/SM +firecracker/SM +fired/U +firedamp/SM +firefight/JRGZS +firefly/MS +fireguard/M +firehouse/MS +firelight/GZSM +fireman/M +firemen +fireplace/MS +fireplug/MS +firepower/SM +fireproof/SGD +firer/M +firesafe +fireside/SM +firestorm/SM +firetrap/SM +firetruck/S +firewall/S +firewater/SM +firewood/MS +firework/MS +firing/M +firkin/M +firm's +firm/ISFDG +firmament/MS +firmer +firmest +firmly/I +firmness/MS +firmware/MS +firring +first/SY +firstborn/S +firsthand +firth/M +firths +fiscal/YS +fish/JGZMSRD +fishbowl/MS +fishcake/S +fisher/M +fisherman/M +fishermen/M +fishery/MS +fishhook/MS +fishily +fishiness/MS +fishing/M +fishmeal +fishmonger/MS +fishnet/SM +fishpond/SM +fishtail/DMGS +fishtanks +fishwife/M +fishwives +fishy/TPR +fissile +fission/BSDMG +fissionable/S +fissure/MGSD +fist/MDGS +fistfight/SM +fistful/MS +fisticuff/SM +fistula/SM +fistulous +fit's/K +fit/UYPS +fitful/PY +fitfulness/SM +fitments +fitness/USM +fits/AK +fitted/UA +fitter/SM +fittest +fitting/AU +fittingly +fittingness/M +fittings +five/MRS +fivefold +fiver/M +fix/USDG +fixable +fixate/VNGXSD +fixatifs +fixation/M +fixative/S +fixed/YP +fixedness/M +fixer/SM +fixes/I +fixing/SM +fixity/MS +fixture/SM +fizz/SRDG +fizzer/M +fizzle/GSD +fizzy/RT +fjord/SM +fl/GJD +flab/MS +flabbergast/GSD +flabbergasting/Y +flabbily +flabbiness/SM +flabby/TPR +flaccid/Y +flaccidity/MS +flack/SGDM +flag/MS +flagella/M +flagellate/DSNGX +flagellation/M +flagellum/M +flagged +flagging/SMY +flaggingly/U +flagman/M +flagmen +flagon/SM +flagpole/SM +flagrance/MS +flagrancy/SM +flagrant/Y +flagship/MS +flagstaff/MS +flagstone/SM +flail/SGMD +flair/SM +flak/RDMGS +flake/SM +flaker/M +flakiness/MS +flaky/PRT +flam/MRNDJGZ +flambeing +flambes +flamboyance/MS +flamboyancy/MS +flamboyant/YS +flambé/D +flame's +flame/SIGDR +flamen/M +flamenco/SM +flameproof/DGS +flamer/IM +flamethrower/SM +flaming/Y +flamingo/SM +flammability/ISM +flammable/SI +flan/MS +flange/GMSD +flank/SGZRDM +flanker/M +flannel/DMGS +flannelet/MS +flannelette's +flap/MS +flapjack/SM +flapped +flapper/SM +flapping +flaps/M +flare/SDG +flareup/S +flaring/Y +flash/JMRSDGZ +flashback/SM +flashbulb/SM +flashcard/S +flashcube/MS +flasher/M +flashgun/S +flashily +flashiness/SM +flashing/M +flashlight/MS +flashy/TPR +flask/SM +flat/MYPS +flatbed/S +flatboat/MS +flatcar/MS +flatfeet +flatfish/SM +flatfoot/SGDM +flathead/M +flatiron/SM +flatland/RS +flatmate/M +flatness/MS +flatted +flatten/SDRG +flattener/M +flatter/DRSZG +flatterer/M +flattering/YU +flattery/SM +flattest/M +flatting +flattish +flattop/MS +flatulence/SM +flatulent/Y +flatus/SM +flatware/MS +flatworm/SM +flaunt/SDG +flaunting/Y +flautist/SM +flavor/SJDRMZG +flavored/U +flavorer/M +flavorful +flavoring/M +flavorless +flavorsome +flaw/GDMS +flawless/PY +flawlessness/MS +flax/MSN +flaxseed/M +flay/RDGZS +flayer/M +flea/SM +fleabag/MS +fleabites +fleawort/M +fleck/GRDMS +fledge/GSD +fledged/U +fledgling/SM +flee/RS +fleece/RSDGMZ +fleecer/M +fleeciness/SM +fleecy/RTP +fleeing +fleet/MYRDGTPS +fleeting/YP +fleetingly/M +fleetingness/SM +fleetness/MS +flesh/JMYRSDG +flesher/M +fleshiness/M +fleshless +fleshly/TR +fleshpot/SM +fleshy/TPR +fletch/DRSGJ +fletcher/M +fletching/M +flew/S +flews/M +flex/MSDAG +flexed/I +flexibility/MSI +flexible/I +flexibly/I +flexitime's +flextime/S +flexural +flexure/M +flibbertigibbet/MS +flick/GZSRD +flicker/GD +flickering/Y +flickery +flier/M +flight/GMDS +flightiness/SM +flightless +flightpath +flighty/RTP +flimflam/MS +flimflammed +flimflamming +flimsily +flimsiness/MS +flimsy/PTRS +flinch/GDRS +flincher/M +flinching/U +fling/RMG +flinger/M +flint/MDSG +flintiness/M +flintless +flintlock/MS +flinty/TRP +flip/S +flipflop +flippable +flippancy/MS +flippant/Y +flipped +flipper/SM +flippest +flipping +flirt/GRDS +flirtation/SM +flirtatious/PY +flirtatiousness/MS +flit/S +flitted +flitting +float/SRDGJZ +floater/M +floaty +flocculate/GNDS +flocculation/M +flock/SJDMG +floe/MS +flog/S +flogged +flogger/SM +flogging/SM +flood/SMRDG +floodgate/MS +floodlight/DGMS +floodlit +floodplain/S +floodwater/SM +floor/SJRDMG +floorboard/MS +floorer/M +flooring/M +floorspace +floorwalker/SM +floozy/SM +flop/MS +flophouse/SM +flopped +flopper/M +floppily +floppiness/SM +flopping +floppy/TMRSP +flora/SM +floral/SY +florescence/MIS +florescent/I +floret/MS +florid/YP +floridness/SM +florin/MS +florist/MS +floss/GSDM +flossy/RST +flotation/SM +flotilla/SM +flotsam/SM +flounce/GDS +flouncing/M +flouncy/RT +flounder/SDG +flour/SGDM +flourish/GSRD +flourisher/M +flourishing/Y +floury/TR +flout/GZSRD +flouter/M +flow/ISG +flowchart/SG +flowed +flower's +flower/CSGD +flowerbed/SM +flowerer/M +floweriness/SM +flowerless +flowerpot/MS +flowery/TRP +flowing/Y +flown +flowstone +flt +flu/MS +flub/S +flubbed +flubbing +fluctuate/XSDNG +fluctuation/M +flue/SM +fluency/MS +fluent/SF +fluently +fluff/SGDM +fluffiness/SM +fluffy/PRT +fluid/MYSP +fluidity/SM +fluidized +fluidness/M +fluke/SDGM +fluky/RT +flume/SDGM +flummox/DSG +flung +flunk/SRDG +flunkey's +flunky/MS +fluoresce/GSRD +fluorescence/MS +fluorescent/S +fluoridate/XDSGN +fluoridation/M +fluoride/SM +fluorimetric +fluorinated +fluorine/SM +fluorite/MS +fluorocarbon/MS +fluoroscope/MGDS +fluoroscopic +flurry/GMDS +flush/TRSDPBG +flushness/M +fluster/DSG +flute/SRDGMJ +fluter/M +fluting/M +flutist/MS +flutter/DRSG +flutterer/M +fluttery +flux/IMS +fluxed/A +fluxes/A +fluxing +fly/JGBDRSTZ +flyaway +flyblown +flyby/M +flybys +flycatcher/MS +flyer's +flyleaf/M +flyleaves +flyover/MS +flypaper/MS +flysheet/S +flyspeck/MDGS +flyswatter/S +flyway/MS +flyweight/MS +flywheel/MS +foal/MDSG +foam/MRDSG +foaminess/MS +foamy/RPT +fob/SM +fobbed +fobbing +focal/F +focally +foci/M +focus/SRDMBG +focused/AU +focuser/M +focuses/A +fodder/GDMS +foe/SM +foetid +fog/SM +fogbound +fogged/C +foggily +fogginess/MS +fogging/C +foggy/RPT +foghorn/SM +fogs/C +fogy/SM +fogyish +foible/MS +foil/GSD +foist/GDS +fol/Y +fold/RDJSGZ +foldable/U +foldaway/S +folded/AU +folder/M +foldout/MS +folds/UA +foliage/MSD +foliate/CSDXGN +foliation/CM +folio/SDMG +folk/MS +folklike +folklore/MS +folkloric +folklorist/SM +folksiness/MS +folksinger/S +folksinging/S +folksong/S +folksy/TPR +folktale/S +folkway/S +foll +follicle/SM +follicular +follow/JSZBGRD +follower/M +followup's +folly/SM +foment/RDSG +fomentation/SM +fomenter/M +fond/PMYRDGTS +fondant/SM +fondle/GSRD +fondler/M +fondness/MS +fondue/MS +font/MS +fontanel/MS +fontanelle's +food/MS +foodie/S +foodstuff/MS +fool/MDGS +foolery/MS +foolhardily +foolhardiness/SM +foolhardy/PTR +foolish/PRYT +foolishness/SM +foolproof +foolscap/MS +foot/SMRDGZJ +footage/SM +football/SRDMGZ +footbridge/SM +footer/M +footfall/SM +foothill/SM +foothold/MS +footing/M +footless +footlights +footling +footlocker/SM +footloose +footman/M +footmarks +footmen +footnote/MSDG +footpad/SM +footpath/M +footpaths +footplate/M +footprint/MS +footrace/S +footrest/MS +footsie/SM +footsore +footstep/SM +footstool/SM +footwear/M +footwork/SM +fop/MS +fopped +foppery/MS +fopping +foppish/YP +foppishness/SM +for/HT +forage/GSRDMZ +forager/M +foray/SGMRD +forayer/M +forbade +forbear/MRSG +forbearance/SM +forbearer/M +forbid/S +forbidden +forbidding/YPS +forbiddingness/M +forbore +forborne +force/SRDGM +forced/Y +forcefield/MS +forceful/PY +forcefulness/MS +forceps/M +forcer/M +forcible/P +forcibleness/M +forcibly +ford/SMDBG +fordable/U +fore/S +forearm/GSDM +forebear/MS +forebode/GJDS +foreboding/PYM +forebodingness/M +forecast/SZGR +forecaster/M +forecastle/MS +foreclose/GSD +foreclosure/MS +forecourt/SM +foredoom/SDG +forefather/SM +forefeet +forefinger/MS +forefoot/M +forefront/SM +foregoer/M +foregoing/S +foregone +foregos +foreground/MGDS +forehand/S +forehead/MS +foreign/PRYZS +foreigner/M +foreignness/SM +foreknew +foreknow/GS +foreknowledge/MS +foreknown +foreleg/MS +forelimb/MS +forelock/MDSG +foreman/M +foremast/SM +foremen +foremost +forename/DSM +forenoon/SM +forensic/S +forensically +forensics/M +foreordain/DSG +forepart/MS +forepaws +forepeople +foreperson/S +foreplay/MS +forequarter/SM +forerunner/MS +foresail/SM +foresaw +foresee/ZSRB +foreseeable/U +foreseeing +foreseen/U +foreseer/M +foreshadow/SGD +foreshore/M +foreshorten/DSG +foresight/SMD +foresighted/PY +foresightedness/SM +foreskin/SM +forest's +forest/CSAGD +forestall/LGSRD +forestaller/M +forestallment/M +forestation/MCS +forestations/A +forester/SM +forestland/S +forestry/MS +foretaste/MGSD +foretell/RGS +foreteller/M +forethought/MS +foretold +forever/PS +forevermore +forewarn/GSJRD +forewarner/M +forewent +forewoman/M +forewomen +foreword/SM +forfeit/ZGDRMS +forfeiter/M +forfeiture/MS +forfend/GSD +forgather/GSD +forgave +forge/JVGMZSRD +forged/A +forger/M +forgery/MS +forges/A +forget/SV +forgetful/PY +forgetfulness/SM +forgettable/U +forgettably/U +forgetting +forging/M +forgivable/U +forgivably/U +forgive/SRPBZG +forgiven +forgiveness/SM +forgiver/M +forgiving/UP +forgivingly +forgivingness/M +forgo/RSGZ +forgoer/M +forgoes +forgone +forgot +forgotten/U +fork/GSRDM +forkful/S +forklift/DMSG +forlorn/PTRY +forlornness/M +form's +form/CGSAFDI +formability/AM +formal/IY +formaldehyde/SM +formalin/M +formalism/SM +formalist/SM +formalistic +formality/SMI +formalization/SM +formalize/ZGSRD +formalized/U +formalizer/M +formalizes/I +formalness/M +formals +formant/MIS +format's +format/AVS +formate/MXGNSD +formation/AFSCIM +formative/SYP +formatively/I +formativeness/IM +formatted/UA +formatter's +formatter/A +formatters +formatting/A +formed/U +former/FSAI +formerly +formfitting +formic +formidable/P +formidableness/M +formidably +formless/PY +formlessness/MS +formula/SM +formulaic +formulate/AGNSDX +formulated/U +formulation/AM +formulator/SM +fornicate/GNXSD +fornication/M +fornicator/SM +forsake/SG +forsaken +forsook +forsooth +forswear/SG +forswore +forsworn +forsythia/MS +fort/SM +forte/MS +forthcome/JG +forthcoming/U +forthright/PYS +forthrightness/SM +forthwith +fortieths +fortification/MS +fortified/U +fortifier/SM +fortify/ADSG +fortiori +fortissimo/S +fortitude/SM +fortnight/MYS +fortnightly/S +fortress/GMSD +fortuitous/YP +fortuitousness/SM +fortuity/MS +fortunate/YUS +fortunateness/M +fortune/MGSD +fortuneteller/SM +fortunetelling/SM +forty/SRMH +forum/MS +forward/PTZSGDRY +forwarder/M +forwarding/M +forwardness/MS +forwent +fossil/MS +fossiliferous +fossilization/MS +fossilize/GSD +fossilized/U +foster/SRDG +fosterer/M +fought +foul/SYRDGTP +foulard/SM +foulmouth/D +foulness/MS +fouls/M +found/RDGZS +foundation/SM +foundational +founded/UF +founder's/F +founder/MDG +founding/F +foundling/MS +foundry/MS +founds/KF +fount/MS +fountain/SMDG +fountainhead/SM +four/SHM +fourfold +fourpence/M +fourpenny +fourposter/SM +fourscore/S +foursome/SM +foursquare +fourteen/SMRH +fourteener/M +fourteenths +fourth/Y +fourths +fovea/M +fowl/SGMRD +fowler/M +fowling/M +fox/MDSG +foxfire/SM +foxglove/SM +foxhole/SM +foxhound/SM +foxily +foxiness/MS +foxing/M +foxtail/M +foxtrot/MS +foxtrotted +foxtrotting +foxy/TRP +foyer/SM +fps +fr +fracas/SM +fractal/SM +fraction/ISMA +fractional/Y +fractionate/DNG +fractionation/M +fractioned +fractioning +fractious/PY +fractiousness/SM +fracture/MGDS +fragile/Y +fragility/MS +fragment/SDMG +fragmentarily +fragmentariness/M +fragmentary/P +fragmentation/MS +fragrance/SM +fragrant/Y +frail/STPYR +frailness/MS +frailty/MS +frame/SRDJGMZ +framed/U +framer/M +framework/SM +framing/M +franc/SM +franchise's +franchise/ESDG +franchisee/S +franchiser/SM +francium/MS +francophone/M +frangibility/SM +frangible +frank/SGTYRDP +franker/M +frankfurter/MS +frankincense/MS +franklin/M +frankness/MS +frantic/PY +frantically +franticness/M +frappeed +frappeing +frappes +frappé +frat/MS +fraternal/Y +fraternity/MSF +fraternization/SM +fraternize/GZRSD +fraternizer/M +fraternizing/U +fratricidal +fratricide/MS +fraud's +fraud/CS +fraudsters +fraudulence/S +fraudulent/YP +fraught/SGD +fray's +fray/CSDG +frazzle/GDS +freak/SGDM +freakish/YP +freakishness/SM +freaky/RT +freckle/GMDS +freckly/RT +free/YTDRSP +freebase/GDS +freebie/MS +freeboot/ZR +freebooter/M +freeborn +freedman/M +freedmen +freedom/MS +freehand/D +freehanded/Y +freehold/ZSRM +freeholder/M +freeing/S +freelance/SRDGZM +freeload/SRDGZ +freeloader/M +freeman/M +freemasonry/M +freemen +freeness/M +freestanding +freestone/SM +freestyle/SM +freethinker/MS +freethinking/S +freeway/MS +freewheel/SRDMGZ +freewheeler/M +freewheeling/P +freewill +freezable +freeze/UGSA +freezer/SM +freezing/S +freight/ZGMDRS +freighter/M +frenetic/S +frenetically +frenzied/Y +frenzy/MDSG +freon/S +freq +frequency/ISM +frequent/IY +frequented/U +frequenter/MS +frequentest +frequenting +frequentness/M +frequents +fresco/DMG +frescoes +fresh/AZSRNDG +freshen/SZGDR +freshener/M +fresher/MA +freshest +freshet/SM +freshly +freshman/M +freshmen +freshness/MS +freshwater/SM +fret/S +fretboard +fretful/PY +fretfulness/MS +fretsaw/S +fretted +fretting +fretwork/MS +friable/P +friableness/M +friar/YMS +friary/MS +fricassee/MSD +fricasseeing +frication/M +fricative/MS +friction/MS +frictional/Y +frictionless/Y +fridge/SM +fried/A +friedcake/SM +friend/SGMYD +friendless/P +friendlessness/M +friendlies +friendlily +friendliness/USM +friendly/PUTR +friendship/MS +frier's +fries/M +frieze/SDGM +frig/S +frigate/SM +frigged +frigging/S +fright/GXMDNS +frighten/DG +frightening/Y +frightful/PY +frightfulness/MS +frigid/YP +frigidity/MS +frigidness/SM +frill/MDGS +frilly/RST +fringe's +fringe/IGSD +frippery/SM +frisk/RDGS +frisker/M +friskily +friskiness/SM +frisky/RTP +frisson/M +fritter/RDSG +fritterer/M +fritz/SM +frivolity/MS +frivolous/PY +frivolousness/SM +frizz/GYSD +frizzle/DSG +frizzly/RT +frizzy/RT +fro/HS +frock's +frock/SUDGC +frocking/M +frog/MS +frogged +frogging +frogman/M +frogmarched +frogmen +frolic/SM +frolicked +frolicker/SM +frolicking +frolicsome +from +frond/SM +front's +front/GSFRD +frontage/MS +frontal/SY +frontier/SM +frontiersman/M +frontiersmen +frontispiece/SM +frontrunner's +frontward/S +frosh/M +frost's +frost/CDSG +frostbit/G +frostbite/MS +frostbiting/M +frostbitten +frosted/U +frosteds +frostily +frostiness/SM +frosting/MS +frosty/PTR +froth/GMD +frothiness/SM +froths +frothy/TRP +froufrou/MS +froward/P +frowardness/MS +frown/RDSG +frowner/M +frowning/Y +frowzily +frowziness/SM +frowzy/RPT +froze/UA +frozen/YP +frozenness/M +fructify/GSD +fructose/MS +frugal/Y +frugality/SM +fruit/GMRDS +fruitcake/SM +fruiter/RM +fruiterer/M +fruitful/UYP +fruitfuller +fruitfullest +fruitfulness/MS +fruitiness/MS +fruition/SM +fruitless/YP +fruitlessness/MS +fruity/RPT +frump/MS +frumpish +frumpy/TR +frustrate/RSDXNG +frustrater/M +frustrating/Y +frustration/M +frustum/SM +fry/NGDS +fryer/MS +ft/C +fuchsia/MS +fuck/GZJRDMS! +fucker/M! +fuddle/GSD +fudge/GMSD +fuel's +fuel/ASDG +fueler/SM +fugal +fugitive/SYMP +fugitiveness/M +fugue/GMSD +fuhrer/S +fulcrum/SM +fulfill/GLSRD +fulfilled/U +fulfiller/M +fulfillment/MS +full/RDPSGZT +fullback/SMG +fuller/DMG +fullish +fullness/MS +fullstops +fullword/SM +fully +fulminate/XSDGN +fulmination/M +fulness's +fulsome/PY +fulsomeness/SM +fumble/GZRSD +fumbler/M +fumbling/Y +fume/DSG +fumigant/MS +fumigate/NGSDX +fumigation/M +fumigator/SM +fuming/Y +fumy/TR +fun/MS +function/GSMD +functional/YS +functionalism/M +functionalist/SM +functionality/S +functionary/MS +functor/SM +fund/ASMRDZG +fundamental/SY +fundamentalism/SM +fundamentalist/SM +funded/U +fundholders +fundholding +funding/S +funeral/MS +funerary +funereal/Y +funfair/M +fungal/S +fungi/M +fungible/M +fungicidal +fungicide/SM +fungoid/S +fungous +fungus/M +funicular/SM +funk/GSDM +funkiness/S +funky/RTP +funned +funnel/SGMD +funner +funnest +funnily/U +funniness/SM +funning +funny/RSPT +fur/PMS +furbelow/MDSG +furbish/GDRSA +furbisher/M +furious/RYP +furiousness/M +furl/UDGS +furlong/MS +furlough/DGM +furloughs +furn +furnace/GMSD +furnish/GASD +furnished/U +furnisher/MS +furnishing/SM +furniture/SM +furor/MS +furore/MS +furred +furrier/M +furriness/SM +furring/SM +furrow/DMGS +furry/RTZP +further/TGDRS +furtherance/MS +furtherer/M +furthermore +furthermost +furthest +furtive/PY +furtiveness/SM +fury/SM +furze/SM +fuse's/A +fuse/FSDAGCI +fusebox/S +fusee/SM +fuselage/SM +fusibility/SM +fusible/I +fusiform +fusilier/MS +fusillade/SDMG +fusion/KMFSI +fuss/SRDMG +fussbudget/MS +fusser/M +fussily +fussiness/MS +fusspot/SM +fussy/PTR +fustian/MS +fustiness/MS +fusty/RPT +fut +futile/PY +futileness/M +futility/MS +futon/S +future/SM +futurism/SM +futurist/S +futuristic/S +futurity/MS +futurologist/S +futurology/MS +futz/GSD +fuze's +fuzz/SDMG +fuzzily +fuzziness/SM +fuzzy/PRT +fwd +fwy +fête/MS +g's +g/VBX +gab/S +gabardine/SM +gabbed +gabbiness/S +gabbing +gabble/SDG +gabby/TRP +gaberdine's +gabfest/MS +gable/GMSRD +gad/S +gadabout/MS +gadded +gadder/MS +gadding +gadfly/MS +gadget/SM +gadgetry/MS +gadolinium/MS +gaff/SGZRDM +gaffe/MS +gaffer/M +gag/DRSG +gaga +gage/SM +gager/M +gagged +gagging +gaggle/SDG +gagwriter/S +gaiety/MS +gaily +gain/ADGS +gainer/SM +gainful/YP +gainfulness/M +gaining/S +gainly/U +gainsaid +gainsay/RSZG +gainsayer/M +gait/GSZMRD +gaiter/M +gal's +gal/AS +gala/SM +galactic +galaxy/MS +gale's +gale/AS +galen +galena/MS +galenite/M +gall/SGMD +gallant/UY +gallanted +gallanting +gallantry/MS +gallants +gallbladder/MS +galleon/SM +galleria/S +gallery/MSDG +galley/MS +gallimaufry/MS +galling/Y +gallium/SM +gallivant/GDS +gallon/SM +gallonage/M +gallop/GSRDZ +galloper/M +gallows/M +gallstone/MS +galoot/MS +galore/S +galosh/GMSD +galumph/GD +galumphs +galvanic +galvanism/MS +galvanization/SM +galvanize/SDG +galvanometer/SM +galvanometric +gambit/MS +gamble/GZRSD +gambler/M +gambol/SGD +game/PJDRSMYTZG +gamecock/SM +gamekeeper/MS +gameness/MS +gamesmanship/SM +gamesmen +gamest/RZ +gamester/M +gamete/MS +gametic +gamin/MS +gamine/SM +gaminess/MS +gaming/M +gamma/MS +gammon/DMSG +gamut/MS +gamy/TRP +gander/DMGS +gang/GRDMS +gangbusters +ganger/M +gangland/SM +ganglia/M +gangling +ganglion/M +ganglionic +gangplank/SM +gangrene/SDMG +gangrenous +gangster/SM +gangway/MS +gannet/SM +gantlet/GMDS +gantry/MS +gaol/MRDGZS +gaoler/M +gap/SJMDRG +gape/S +gaper/M +gaping/Y +gapped +gapping +gar/SLM +garage/GMSD +garb/DMGS +garbage/SDMG +garbageman/M +garbanzo/MS +garble/RSDG +garbler/M +garden/ZGRDMS +gardener/M +gardenia/SM +gardening/M +garfish/MS +gargantuan +gargle/SDG +gargoyle/DSM +garish/YP +garishness/MS +garland/SMDG +garlic/SM +garlicked +garlicking +garlicky +garment/MDGS +garner/SGD +garnet/SM +garnish/DSLG +garnishee/SDM +garnisheeing +garnishment/MS +garote's +garotte's +garred +garret/SM +garring +garrison/SGMD +garrote/SRDMZG +garroter/M +garrotte's +garrulity/SM +garrulous/PY +garrulousness/MS +garter/SGDM +garçon/SM +gas's +gas/FC +gasbag/MS +gaseous/YP +gaseousness/M +gases/C +gash/GTMSRD +gasification/M +gasifier/M +gasify/SRDGXZN +gasket/SM +gaslight/DMS +gasohol/S +gasoline/MS +gasometer/M +gasp/GZSRD +gasper/M +gasping/Y +gassed/C +gasser/MS +gassiness/M +gassing/SM +gassy/PTR +gastric +gastritides +gastritis/MS +gastroenteritides +gastroenteritis/M +gastrointestinal +gastronome/SM +gastronomic +gastronomical/Y +gastronomy/MS +gastropod/SM +gasworks/M +gate/MGDS +gateau/MS +gateaux +gatecrash/GZSRD +gatehouse/MS +gatekeeper/SM +gatepost/SM +gateway/MS +gather/JRDZGS +gathered/IA +gatherer/M +gathering/M +gathers/A +gator/MS +gauche/TYPR +gaucheness/SM +gaucherie/SM +gaucho/SM +gaudily +gaudiness/MS +gaudy/PRST +gauge/SM +gaugeable +gauger/M +gaunt/PYRDSGT +gauntlet/GSDM +gauntness/MS +gauss's +gauss/C +gausses +gauze/SDGM +gauziness/MS +gauzy/TRP +gave +gavel/GMDS +gavotte/MSDG +gawk/SGRDM +gawkily +gawkiness/MS +gawky/RSPT +gay/RTPS +gayety's +gayness/SM +gaze/DRSZG +gazebo/SM +gazelle/MS +gazer/M +gazette/MGSD +gazetteer/SGDM +gazillion/S +gazpacho/MS +gear/DMJSG +gearbox/SM +gearing/M +gearshift/MS +gearstick +gearwheel/SM +gecko/MS +gee/TDS +geegaw's +geeing +geek/SM +geeky/RT +geese/M +geest/M +geezer/MS +geisha/M +gel/MBS +gelatin/SM +gelatinous/PY +gelatinousness/M +gelcap +geld/JSGD +gelding/M +gelid +gelignite/MS +gelled +gelling +gem/MS +gemlike +gemmed +gemming +gemological +gemologist/MS +gemology/MS +gemstone/SM +gen +gendarme/MS +gender/DMGS +genderless +gene/MS +genealogical/Y +genealogist/SM +genealogy/MS +genera/M +general/MSPY +generalissimo/SM +generalist/MS +generality/MS +generalizable/SM +generalization/MS +generalize/GZBSRD +generalized/U +generalizer/M +generalness/M +generalship/SM +generate/CXAVNGSD +generation/MCA +generational +generative/AY +generator/SM +generators/A +generic/PS +generically +generosity/MS +generous/PY +generously/U +generousness/SM +genes/S +genesis/M +genetic/S +genetically +geneticist/MS +genetics/M +genial/PY +geniality/FMS +genially/F +genialness/M +genie/SM +genies/K +genii/M +genital/YF +genitalia +genitals +genitive/SM +genitourinary +genius/SM +genocidal +genocide/SM +genome/SM +genotype/MS +genre/MS +gent/AMS +genteel/PRYT +genteelness/MS +gentian/SM +gentile/S +gentility/MS +gentle/PRSDGT +gentlefolk/S +gentleman/YM +gentlemanliness/M +gentlemanly/U +gentlemen +gentleness/SM +gentlewoman/M +gentlewomen/M +gently +gentrification/M +gentrify/NSDGX +gentry/MS +genuflect/GDS +genuflection/MS +genuine/PY +genuineness/SM +genus +geocentric +geocentrically +geocentricism +geochemical/Y +geochemistry/MS +geochronology/M +geode/SM +geodesic/S +geodesy/MS +geodetic/S +geog +geographer/MS +geographic +geographical/Y +geography/MS +geologic +geological/Y +geologist/MS +geology/MS +geom +geomagnetic +geomagnetically +geomagnetism/SM +geometer/MS +geometric/S +geometrical/Y +geometrician/M +geometry/MS +geomorphological +geomorphology/M +geophysical/Y +geophysicist/MS +geophysics/M +geopolitic/S +geopolitical/Y +geopolitics/M +geostationary +geosynchronous +geosyncline/SM +geothermal +geothermic +geranium/SM +gerbil/MS +geriatric/S +geriatrics/M +germ/MNS +germane +germanium/SM +germanized +germen/M +germicidal +germicide/MS +germinal/Y +germinate/XVGNSD +germinated/U +germination/M +germinative/Y +gerontocracy/M +gerontological +gerontologist/SM +gerontology/SM +gerrymander/SGD +gerund/SVM +gerundive/M +gestalt/M +gestapo/S +gestate/SDGNX +gestation/M +gestational +gesticulate/XSDVGN +gesticulation/M +gesticulative/Y +gestural +gesture/SDMG +gesundheit +get/S +getaway/SM +getter/SDM +getting +getup/MS +gewgaw/MS +geyser/GDMS +ghastliness/MS +ghastly/TPR +ghat/MS +gherkin/SM +ghetto/DGMS +ghettoize/SDG +ghost/SMYDG +ghostlike +ghostliness/MS +ghostly/TRP +ghostwrite/RSGZ +ghostwritten +ghostwrote +ghoul/SM +ghoulish/PY +ghoulishness/SM +giant/SM +giantess/MS +giantkiller +gibber/DGS +gibberish/MS +gibbet/MDSG +gibbon/MS +gibbous/YP +gibbousness/M +gibe/GDRS +giber/M +giblet/MS +giddap +giddily +giddiness/SM +giddy/GPRSDT +gift/SGMD +gifted/PY +giftedness/M +gig/MS +gigabyte/S +gigacycle/MS +gigahertz/M +gigantic/P +gigantically +giganticness/M +gigavolt +gigawatt/M +gigged +gigging +giggle/RSDGZ +giggler/M +giggling/Y +giggly/TR +gigolo/MS +gila +gilbert/M +gild/JSGZRD +gilder/M +gilding/M +gill/SGMRD +gilt/S +gimbaled +gimbals +gimcrack/S +gimcrackery/SM +gimlet/MDSG +gimme/S +gimmick/GDMS +gimmickry/MS +gimmicky +gimp/GSMD +gimpy/RT +gin/MS +ginger/SGDYM +gingerbread/SM +gingerliness/M +gingerly/P +gingersnap/SM +gingery +gingham/SM +gingivitis/SM +ginkgo/M +ginkgoes +ginmill +ginned +ginning +ginseng/SM +giraffe/MS +gird/RDSGZ +girded/U +girder/M +girdle/GMRSD +girdler/M +girl/MS +girlfriend/MS +girlhood/SM +girlie/M +girlish/YP +girlishness/SM +giro/M +girt/GDS +girth/MDG +girths +gist/MS +git/M +give/HZGRS +giveaway/SM +giveback/S +given/SP +giver/M +giving/Y +gizmo's +gizzard/SM +glacial/Y +glaciate/XNGDS +glaciation/M +glacier/SM +glaciological +glaciologist/M +glaciology/M +glacé/DGS +glad/YSP +gladded +gladden/GDS +gladder +gladdest +gladding +gladdy +glade/SM +gladiator/SM +gladiatorial +gladiola/MS +gladioli +gladiolus/M +gladly/RT +gladness/MS +gladsome/RT +glamor/DMGS +glamorization/MS +glamorize/SRDZG +glamorizer/M +glamorous/PY +glamorousness/M +glance/GJSD +glancing/Y +gland/ZSM +glanders/M +glandes +glandular/Y +glans/M +glare/SDG +glaring/YP +glaringness/M +glasnost/S +glass/GSDM +glassblower/S +glassblowing/MS +glassful/MS +glasshouse/SM +glassily +glassiness/SM +glassless +glassware/SM +glasswort/M +glassy/PRST +glaucoma/SM +glaucous +glaze/SRDGZJ +glazed/U +glazer/M +glazier/SM +glazing/M +gleam/MDGS +glean/RDGZJS +gleaner/M +gleaning/M +glee/DSM +gleed/M +gleeful/YP +gleefulness/MS +gleeing +glen/SM +glib/YP +glibber +glibbest +glibness/MS +glide/JGZSRD +glider/M +glim/M +glimmer/DSJG +glimmering/M +glimpse/DRSZMG +glimpser/M +glint/DSG +glissandi +glissando/M +glisten/DSG +glister/DGS +glitch/MS +glitter/GDSJ +glittering/Y +glittery +glitz/GSD +glitzy/TR +gloaming/MS +gloat/SRDG +gloater/M +gloating/Y +glob/GDMS +global/SY +globalism/S +globalist/S +globe/SM +globetrotter/MS +globular/PY +globularity/M +globularness/M +globule/MS +globulin/MS +glockenspiel/SM +glommed +gloom/GSMD +gloomily +gloominess/MS +gloomy/RTP +glop/MS +glopped +glopping +gloppy/TR +glorification/M +glorifier/M +glorify/XZRSDNG +glorious/IYP +gloriousness/IM +glory/SDMG +gloss/GSDM +glossary/MS +glossily +glossiness/SM +glossolalia/SM +glossy/RSPT +glottal +glottalization/M +glottis/MS +glove/SRDGMZ +gloveless +glover/M +glow/GZRDMS +glower/GD +glowing/Y +glowworm/SM +glucose/SM +glue/DRSMZG +glued/U +gluer/M +gluey +gluier +gluiest +glum/SYP +glummer +glummest +glumness/MS +gluon/M +glut/SMNX +glutamate/M +gluten/M +glutenous +glutinous/PY +glutinousness/M +glutted +glutting +glutton/MS +gluttonous/Y +gluttony/SM +glyceride/M +glycerin/SM +glycerinate/MD +glycerine's +glycerol/SM +glycerolized/C +glycine/M +glycogen/SM +glycol/MS +glyph/M +glyphs +gm +gnarl/SMDG +gnash/SDG +gnat/MS +gnaw/GRDSJ +gnawer/M +gnawing/M +gneiss/SM +gnome/SM +gnomelike +gnomic +gnomish +gnomonic +gnostic/K +gnosticism +gnu/MS +go/MRHZGJ +goad/MDSG +goal/MDSG +goalie/SM +goalkeeper/MS +goalkeeping/M +goalless +goalmouth/M +goalpost/S +goalscorer +goalscoring +goaltender/SM +goat/MS +goatee/SM +goatherd/MS +goatskin/SM +gob/SM +gobbed +gobbet/MS +gobbing +gobble/SRDGZ +gobbledegook's +gobbledygook/S +gobbler/M +goblet/MS +goblin/SM +god/SMY +godchild/M +godchildren +goddammit +goddamn/GS +goddaughter/SM +godded +goddess/MS +godding +godfather/GSDM +godforsaken +godhead/S +godhood/SM +godless/P +godlessness/MS +godlike/P +godlikeness/M +godliness/UMS +godly/UTPR +godmother/MS +godparent/SM +godsend/MS +godson/MS +goer/MG +goes +gofer/SM +goggle/SRDGZ +goggler/M +going/M +goiter/SM +gold/MRNGTS +goldbrick/GZRDMS +goldbricker/M +golden/TRYP +goldenness/M +goldenrod/SM +goldenseal/M +goldfinch/MS +goldfish/SM +goldmine/S +goldsmith/M +goldsmiths +golf/RDMGZS +golfer/M +golly/S +gonad/SM +gonadal +gondola/SM +gondolier/MS +gone/RZN +goner/M +gong/SGDM +gonion/M +gonna +gonorrhea/MS +gonorrheal +goo/MS +goober/MS +good/SYP +goodbye/MS +goodhearted +goodie's +goodish +goodly/TR +goodness/MS +goodnight +goodwill/MS +goody/SM +gooey +goof/SDMG +goofiness/MS +goofy/RPT +gooier +gooiest +gook/SM +goon/SM +goop/SM +goos/SDG +goose/M +gooseberry/MS +goosebumps +gopher/SM +gore/DSMG +gorge/GMSRD +gorged/E +gorgeous/YP +gorgeousness/SM +gorger/EM +gorges/E +gorging/E +gorgon/S +gorilla/MS +gorily +goriness/MS +goring/M +gormandize/SRDGZ +gormandizer/M +gormless +gorp/S +gorse/SM +gory/PRT +gos +gosh/S +goshawk/MS +gosling/M +gospel/MRSZ +gospeler/M +gossamer/SM +gossip/ZGMRDS +gossipy +got/IU +gotcha/SM +goto +gotta +gotten/U +gouge/GZSRD +gouger/M +goulash/SM +gourd/MS +gourde/SM +gourmand/MS +gourmet/MS +gout/SM +gouty/RT +gov/S +govern/LBGSD +governable/U +governance/SM +governed/U +governess/SM +government/MS +governmental/Y +governor/MS +governorship/SM +govt +gown/GSDM +gr +grab/S +grabbed +grabber/SM +grabbing/S +grace/ESDMG +graceful/EYPU +gracefuller +gracefullest +gracefulness/ESM +graceless/PY +gracelessness/MS +gracious/UY +graciousness/SM +grackle/SM +grad/MRDGZJS +gradate/DSNGX +gradation/MCS +grade's +grade/ACSDG +graded/U +gradely +grader/MC +gradient/RMS +gradual/SYP +gradualism/MS +gradualist/MS +gradualness/MS +graduand/SM +graduate/MNGDSX +graduation/M +graffiti +graffito/M +graft/MRDSGZ +grafter/M +grafting/M +graham/SM +grail/S +grain's +grain/IGSD +grainer/M +graininess/MS +graining/M +grainy/RTP +gram/KSM +grammar/MS +grammarian/SM +grammatic/K +grammatical/UY +grammaticality/M +grammaticalness/M +gramme/SM +gramophone/SM +grampus/SM +granary/MS +grand/TPSYR +grandam/SM +grandaunt/MS +grandchild/M +grandchildren +granddad/SM +granddaddy/MS +granddaughter/MS +grandee/SM +grandeur/MS +grandfather/MYDSG +grandiloquence/SM +grandiloquent/Y +grandiose/YP +grandiosity/MS +grandkid/SM +grandma/MS +grandmaster/MS +grandmother/MYS +grandnephew/MS +grandness/MS +grandniece/SM +grandpa/MS +grandparent/MS +grandson/MS +grandstand/SRDMG +grandstander/M +granduncle/MS +grange/MSR +granite/MS +granitic +granny/MS +granola/S +grant/SGZMRD +grantee/MS +granter/M +grantor's +grantsmanship/S +granular/Y +granularity/SM +granulate/SDXVGN +granulation/M +granule/SM +granulocytic +grape/SDGM +grapefruit/SM +grapeshot/M +grapevine/MS +graph/GMD +grapheme/M +graphic/PS +graphical/Y +graphicness/M +graphics/M +graphite/SM +graphologist/SM +graphology/MS +graphs +grapnel/SM +grapple/DRSG +grappler/M +grappling/M +grasp/SRDBG +grasper/M +grasping/PY +graspingness/M +grass/GZSDM +grasshopper/SM +grassland/MS +grassroots +grassy/RT +grate/SRDJGZ +grateful/YPU +gratefuller +gratefullest +gratefulness/USM +grater/M +grates/I +graticule/M +gratification/M +gratified/U +gratify/NDSXG +gratifying/Y +grating/YM +gratis +gratitude/IMS +gratuitous/PY +gratuitousness/MS +gratuity/SM +gravamen/SM +grave/SRDPGMZTY +gravedigger/SM +gravel/SGMYD +graven +graveness/MS +graver/M +graveside/S +gravestone/SM +graveyard/MS +gravid/PY +gravidness/M +gravimeter/SM +gravimetric +gravitas +gravitate/XVGNSD +gravitation/M +gravitational/Y +graviton/SM +gravity/MS +gravy/SM +gray/PYRDGTS +graybeard/MS +grayish +grayness/S +graze/GZSRD +grazer/M +grazing/M +grease/GMZSRD +greasepaint/MS +greaseproof +greaser/M +greasily +greasiness/SM +greasy/PRT +great/SPTYRN +greatcoat/DMS +greaten/DG +greathearted +greatness/MS +grebe/MS +greed's +greed/C +greedily +greediness/SM +greeds +greedy/RTP +green/SYRDMPGT +greenback/MS +greenbelt/S +greenery/MS +greenfield +greenfly/M +greengage/SM +greengrocer/SM +greengrocery/M +greenhorn/SM +greenhouse/SM +greening/M +greenish/P +greenmail/GDS +greenness/MS +greenroom/SM +greensward/SM +greenwood/MS +greet/SRDJGZ +greeter/M +greeting/M +greets/A +gregarious/PY +gregariousness/MS +gremlin/SM +grenade/MS +grenadier/SM +grenadine/SM +grew/A +greybeard/M +greyhound/MS +greyness/M +grid/SGM +gridded +griddle/DSGM +griddlecake/SM +gridiron/GSMD +gridlock/DSG +grids/A +grief/MS +grievance/SM +grieve/SRDGZ +griever/M +grieving/Y +grievous/PY +grievousness/SM +griffin/SM +griffon's +grill/RDGS +grille/SM +griller/M +grillwork/M +grim/PGYD +grimace/DRSGM +grimacer/M +grime/MS +griminess/MS +grimmer +grimmest +grimness/MS +grimy/TPR +grin/S +grind/ASG +grinder/MS +grinding/SY +grindstone/SM +gringo/SM +grinned +grinner/M +grinning/Y +grip/SGZMRD +gripe/S +griper/M +grippe/GMZSRD +gripper/M +gripping/Y +grisliness/SM +grisly/RPT +grist/MYS +gristle/SM +gristliness/M +gristly/TRP +gristmill/MS +grit/MS +gritted +gritter/MS +grittiness/SM +gritting +gritty/PRT +grizzle/DSG +grizzling/M +grizzly/TRS +groan/GZSRDM +groaner/M +groat/SM +grocer/MS +grocery/MS +grog/MS +groggily +grogginess/SM +groggy/RPT +groin/MGSD +grok/S +grokked +grokking +grommet/GMDS +groofs +groom/GZSMRD +groomer/M +groomsman/M +groomsmen +groove/SRDGM +groover/M +groovy/TR +grope/SRDJGZ +groper/M +grosbeak/SM +grosgrain/MS +gross/GTYSRDP +grossness/MS +grotesque/PSY +grotesqueness/MS +grotto/M +grottoes +grouch/GDS +grouchily +grouchiness/MS +grouchy/RPT +ground/JGZMDRS +groundbreaking/S +grounded/U +grounder/M +groundhog/SM +groundless/YP +groundlessness/M +groundnut/MS +groundsheet/M +groundskeepers +groundsman/M +groundswell/S +groundwater/S +groundwork/SM +group/ZJSMRDG +grouped/A +grouper/M +groupie/MS +grouping/M +groups/A +grouse/GMZSRD +grouser/M +grout/GSMRD +grouter/M +grove/SRMZ +grovel/SDRGZ +groveler/M +grovelike +groveling/Y +grow/GZYRHS +grower/M +growing/I +growingly +growl/RDGZS +growler/M +growling/Y +growly/RP +grown/IA +grownup/MS +grows/A +growth/IMA +growths/IA +grub/MS +grubbed +grubber/SM +grubbily +grubbiness/SM +grubbing +grubby/RTP +grubstake/MSDG +grudge/GMSRDJ +grudger/M +grudging/Y +gruel/MDGJS +grueling/Y +gruesome/RYTP +gruesomeness/SM +gruff/PSGTYRD +gruffness/MS +grumble/GZJDSR +grumbler/M +grumbling/Y +grump/MDGS +grumpily +grumpiness/MS +grumpy/TPR +grunge/S +grungy/RT +grunion/SM +grunt/SGRD +grunter/M +gryphon's +gs/A +gt +guacamole/MS +guanine/MS +guano/MS +guarani/SM +guarantee/RSDZM +guaranteeing +guarantor/SM +guaranty/MSDG +guard/RDSGZ +guarded/UYP +guardedness/UM +guarder/M +guardhouse/SM +guardian/SM +guardianship/MS +guardrail/SM +guardroom/SM +guardsman/M +guardsmen +guava/SM +gubernatorial +gudgeon/M +guernsey/S +guerrilla/MS +guess/BGZRSD +guessable/U +guessed/U +guesser/M +guesstimate/DSMG +guesswork/MS +guest/SGMD +guff/SM +guffaw/GSDM +guidance/MS +guide/GZSRD +guidebook/SM +guided/U +guideline/SM +guidepost/MS +guider/M +guild/SZMR +guilder/M +guildhall/SM +guile/SDGM +guileful +guileless/YP +guilelessness/MS +guillemot/MS +guillotine/SDGM +guilt/SM +guiltily +guiltiness/MS +guiltless/YP +guiltlessness/M +guilty/PTR +guinea/SM +guise's +guise/SDEG +guitar/SM +guitarist/SM +gulag/S +gulch/MS +gulden/MS +gulf/DMGS +gull/MDSG +gullet/MS +gulley's +gullibility/MS +gullible +gully/SDMG +gulp/RDGZS +gum/MS +gumbo/MS +gumboil/MS +gumboots +gumdrop/SM +gummed +gumminess/M +gumming/C +gummy/RTP +gumption/SM +gumshoe/SDM +gumshoeing +gumtree/MS +gun/MS +gunboat/MS +gunfight/SRMGZ +gunfighter/M +gunfire/SM +gunflint/M +gunfought +gunk/SM +gunky/RT +gunman/M +gunmen +gunmetal/MS +gunned +gunnel's +gunner/SM +gunnery/MS +gunning/M +gunny/SM +gunnysack/SM +gunpoint/MS +gunpowder/SM +gunrunner/MS +gunrunning/MS +gunship/S +gunshot/SM +gunsling/GZR +gunslinger/M +gunsmith/M +gunsmiths +gunwale/MS +guppy/SM +gurgle/SDG +gurney/S +guru/MS +gush/SRDGZ +gusher/M +gushy/TR +gusset/MDSG +gussy/GSD +gust/MDGS +gustatory +gusted/E +gustily +gustiness/M +gusting/E +gusto/M +gustoes +gusts/E +gusty/RPT +gut/SM +gutless/P +gutlessness/S +guts/R +gutser/M +gutsiness/M +gutsy/PTR +gutted +gutter/GSDM +guttering/M +guttersnipe/M +gutting +guttural/SPY +gutturalness/M +gutty/RSMT +guy/MDRZGS +guzzle/GZRSD +guzzler/M +gym/MS +gymkhana/SM +gymnasia's +gymnasium/SM +gymnast/SM +gymnastic/S +gymnastically +gymnastics/M +gymnosperm/SM +gynecologic +gynecological/MS +gynecologist/SM +gynecology/MS +gyp/S +gypped +gypper/S +gypping +gypsite +gypster/S +gypsum/MS +gypsy/SDMG +gyrate/XNGSD +gyration/M +gyrator/MS +gyrfalcon/SM +gyro/MS +gyrocompass/M +gyroscope/SM +gyroscopic +gyve/GDS +h'm +h's +h/EMS +ha/H +habeas +haberdasher/SM +haberdashery/SM +habiliment/SM +habit's +habit/IBDGS +habitability/MS +habitable/P +habitableness/M +habitant/ISM +habitat/MS +habitation/MI +habitations +habitual/SYP +habitualness/SM +habituate/SDNGX +habituation/M +habitué/MS +hacienda/MS +hack/GZSDRBJ +hacker/M +hackle/RSDMG +hackler/M +hackney/SMDG +hacksaw/SDMG +hackwork/S +had/GD +haddock/MS +hades +hadj's +hadji's +hadn't +hadron/MS +hadst +haemoglobin's +haemophilia's +haemorrhage's +hafnium/MS +haft/GSMD +hag/SMN +haggard/SYP +haggardness/MS +hagged +hagging +haggis/SM +haggish +haggle/RSDZG +haggler/M +hagiographer/SM +hagiography/MS +hahnium/S +haiku/M +hail/SGMDR +hailer/M +hailstone/SM +hailstorm/SM +hair/SDM +hairball/SM +hairbreadth/M +hairbreadths +hairbrush/SM +haircare +haircloth/M +haircloths +haircut/MS +haircutting +hairdo/SM +hairdresser/SM +hairdressing/SM +hairdryer/S +hairiness/MS +hairless/P +hairlessness/M +hairlike +hairline/SM +hairnet/MS +hairpiece/MS +hairpin/MS +hairsbreadth +hairsbreadths +hairsplitter/SM +hairsplitting/MS +hairspray +hairspring/SM +hairstyle/SMG +hairstylist/S +hairy/PTR +hajj/M +hajjes +hajji/MS +hake/MS +halal/S +halalled +halalling +halberd/SM +halcyon/S +hale/ISRDG +haler/IM +halest +half/PM +halfback/SM +halfbreed +halfhearted/PY +halfheartedness/MS +halfpence/S +halfpenny/MS +halfpennyworth +halftime/S +halftone/MS +halfway +halfword/MS +halibut/SM +halide/SM +halite/MS +halitoses +halitosis/M +hall/SMR +hallelujah +hallelujahs +halliard's +hallmark/SGMD +hallo/GDS +halloo's +hallow/UD +hallowing +hallows +hallucinate/VNGSDX +hallucination/M +hallucinatory +hallucinogen/SM +hallucinogenic/S +hallway/SM +halo/SDMG +halocarbon +halogen/SM +halogenated +halon +halt/GZJSMDR +halter/GDM +halting/Y +halve/GZDS +halves/M +halyard/MS +ham/SM +hamburg/SZRM +hamburger/M +hamlet/MS +hammed +hammer/ZGSRDM +hammerer/M +hammerhead/SM +hammering/M +hammerless +hammerlock/MS +hammertoe/SM +hamming +hammock/MS +hammy/RT +hamper/GSD +hampered/U +hamster/MS +hamstring/MGS +hamstrung +hand's +hand/UDSG +handbag/MS +handbagged +handbagging +handball/SM +handbarrow/MS +handbasin +handbill/MS +handbook/SM +handbrake/M +handcar/SM +handcart/MS +handclasp/MS +handcraft/GMDS +handcuff/GSD +handcuffs/M +handed/PY +handedness/M +hander/S +handful/SM +handgun/SM +handhold/M +handicap/SM +handicapped +handicapper/SM +handicapping +handicraft/SMR +handicraftsman/M +handicraftsmen +handily/U +handiness/SM +handiwork/MS +handkerchief/MS +handle/MZGRSD +handleable +handlebar/SM +handler/M +handless +handling/M +handmade +handmaid/NMSX +handmaiden/M +handout/SM +handover +handpick/GDS +handrail/SM +handsaw/SM +handset/SM +handshake/GMSR +handshaker/M +handshaking/M +handsome/RPTY +handsomely/U +handsomeness/MS +handspike/SM +handspring/SM +handstand/MS +handwork/SM +handwoven +handwrite/GSJ +handwriting/M +handwritten +handy/URT +handyman/M +handymen +hang/GDRZBSJ +hangar/SGDM +hangdog/S +hanged/A +hanger/M +hanging/M +hangman/M +hangmen +hangnail/MS +hangout/MS +hangover/SM +hangs/A +hangup/S +hank/GZDRMS +hanker/GRDJ +hankerer/M +hankering/M +hankie/SM +hanky's +hansom/MS +hap/SMY +haphazard/SPY +haphazardness/SM +hapless/YP +haplessness/MS +haploid/S +happed +happen/JDGS +happening/M +happenstance/SM +happily/U +happiness/UMS +happing +happy/UTPR +harangue/GDRS +haranguer/M +harass/LSRDZG +harasser/M +harassment/SM +harbinger/DMSG +harbor/ZGRDMS +harborer/M +hard/YNRPJGXTS +hardback/SM +hardball/SM +hardboard/SM +hardboiled +hardbound +hardcore/MS +hardcover/SM +harden/ZGRD +hardened/U +hardener/M +hardening/M +hardhat/S +hardheaded/YP +hardheadedness/SM +hardhearted/YP +hardheartedness/SM +hardihood/MS +hardily +hardiness/SM +hardliner/S +hardness/MS +hardscrabble +hardshell +hardship/MS +hardstand/S +hardtack/MS +hardtop/MS +hardware/SM +hardwire/DSG +hardwood/MS +hardworking +hardy/PTRS +hare/MGDS +harebell/MS +harebrained +harelip/MS +harelipped +harem/SM +hark/GDS +harlequin/MS +harlot/SM +harlotry/MS +harm/MDRGS +harmed/U +harmer/M +harmful/PY +harmfulness/MS +harmless/YP +harmlessness/SM +harmonic/S +harmonica/MS +harmonically +harmonics/M +harmonious/IPY +harmoniousness's/I +harmoniousness/MS +harmonium/MS +harmonization's +harmonization/A +harmonizations +harmonize/ZGSRD +harmonized/U +harmonizer/M +harmonizes/UA +harmony/EMS +harness/DRSMG +harnessed/U +harnesser/M +harnesses/U +harp/MDRJGZS +harper/M +harping/M +harpist/SM +harpoon/SZGDRM +harpooner/M +harpsichord/SM +harpsichordist/MS +harpy/SM +harridan/SM +harrier/M +harrow/RDMGS +harrower/M +harrumph/SDG +harry/RSDGZ +harsh/TRNYP +harshen/GD +harshness/SM +hart/MS +harvest/MDRZGS +harvested/U +harvester/M +harvestman/M +has +hash's +hash/AGSD +hasher/M +hashing/M +hashish/MS +hasn't +hasp/GMDS +hassle/MGRSD +hassock/MS +hast/GXJDN +haste/MS +hasten/GRD +hastener/M +hastily +hastiness/MS +hasty/RPT +hat/MDRSZG +hatch/RSDJG +hatchback/SM +hatcheck/S +hatched/U +hatcher/M +hatchery/MS +hatchet/MDSG +hatching/M +hatchway/MS +hate/S +hateful/YP +hatefulness/MS +hater/M +hatless +hatred/SM +hatstands +hatted +hatter/SM +hatting +hauberk/SM +haughtily +haughtiness/SM +haughty/TPR +haul/SDRGZ +haulage/MS +hauler/M +haunch/GMSD +haunt/JRDSZG +haunter/M +haunting/Y +hauteur/MS +have/ZGSR +haven't +haven/DMGS +haver/G +haversack/SM +havoc/SM +havocked +havocking +haw/MDSG +hawk/GZSDRM +hawker/M +hawking/M +hawkish/P +hawkishness/S +haws/RZ +hawser/M +hawthorn/MS +hay/GSMDR +haycock/SM +hayfield/MS +hayloft/MS +haymow/MS +hayrick/MS +hayride/MS +hayseed/MS +haystack/SM +haywain +haywire/MS +hazard/MDGS +hazardous/PY +hazardousness/M +haze/DSRJMZG +hazel/MS +hazelnut/SM +hazer/M +hazily +haziness/MS +hazing/M +hazy/PTR +hdqrs +he'd +he'll +he/VMZ +head/SJGZMDR +headache/MS +headband/SM +headboard/MS +headcount +headdress/MS +header/M +headfirst +headgear/SM +headhunt/ZGSRDMJ +headhunter/M +headhunting/M +headily +headiness/S +heading/M +headlamp/S +headland/MS +headless/P +headlessness/M +headlight/MS +headline/DRSZMG +headliner/M +headlock/MS +headlong +headman/M +headmaster/MS +headmastership/M +headmen +headmistress/MS +headphone/SM +headpiece/SM +headpin/MS +headquarter/GDS +headrest/MS +headroom/SM +headscarf/M +headset/SM +headship/SM +headshrinker/MS +headsman/M +headsmen +headstall/SM +headstand/MS +headstock/M +headstone/MS +headstrong +headwaiter/SM +headwall/S +headwater/S +headway/MS +headwind/SM +headword/MS +heady/PTR +heal/DRHSGZ +healed/U +healer/M +health/M +healthful/U +healthfully +healthfulness/SM +healthily/U +healthiness/MSU +healths +healthy/URPT +heap/SMDG +hear/ZTSRHJG +heard/UA +hearer/M +hearing/AM +hearken/SGD +hears/SDAG +hearsay/SM +hearse/M +heart/SMDNXG +heartache/SM +heartbeat/MS +heartbreak/GMS +heartbreaking/Y +heartbroke +heartbroken +heartburn/SGM +heartburning/M +hearted/Y +hearten/EGDS +heartening/EY +heartfelt +hearth/M +hearthrug +hearths +hearthstone/MS +heartily +heartiness/SM +heartland/SM +heartless/YP +heartlessness/SM +heartrending/Y +heartsick/P +heartsickness/MS +heartstrings +heartthrob/MS +heartwarming +heartwood/SM +hearty/TRSP +heat/SMDRGZBJ +heated/UA +heatedly +heater/M +heath/MRNZX +heathen/M +heathendom/SM +heathenish/Y +heathenism/MS +heather/M +heathery +heathland +heaths +heatproof +heats/A +heatstroke/MS +heatwave +heave/DSRGZ +heaven/SYM +heavenliness/M +heavenly/PTR +heavenward/S +heaver/M +heaves/M +heavily +heaviness/MS +heavy/TPRS +heavyhearted +heavyset +heavyweight/SM +hebephrenic +hecatomb/M +heck/S +heckle/RSDZG +heckler/M +hectare/MS +hectic/S +hectically +hectogram/MS +hectometer/SM +hector/SGD +hedge/DSRGMZ +hedgehog/MS +hedgehop/S +hedgehopped +hedgehopping +hedger/M +hedgerow/SM +hedging/Y +hedonism/SM +hedonist/MS +hedonistic +heed/SMGD +heeded/U +heedful/PY +heedfulness/M +heeding/U +heedless/YP +heedlessness/SM +heehaw/DGS +heel/SGZMDR +heeler/M +heeling/M +heelless +heft/GSD +heftily +heftiness/SM +hefty/TRP +hegemonic +hegemony/MS +hegira/S +heifer/MS +height/SMNX +heighten/GD +heinous/PY +heinousness/SM +heir/SDMG +heiress/MS +heirloom/MS +heist/GSMRD +heister/M +held +helical/Y +helices/M +helicon/M +helicopter/GSMD +heliocentric +heliography/M +heliosphere +heliotrope/SM +heliport/MS +helium/MS +helix/M +hell/GSMDR +hellbender/M +hellbent +hellcat/SM +hellebore/SM +heller/M +hellfire/M +hellhole/SM +hellion/SM +hellish/PY +hellishness/SM +hello/GMS +helluva +helm's +helm/U +helmed +helmet/GSMD +helming +helms +helmsman/M +helmsmen +helot/S +help/GZSJDR +helper/M +helpful/UY +helpfulness/MS +helping/M +helpless/YP +helplessness/SM +helpline/S +helpmate/SM +helpmeet's +helve/GMDS +hem/MS +hematite/MS +hematologic +hematological +hematologist/SM +hematology/MS +heme/MS +hemisphere/MSD +hemispheric +hemispherical +hemline/SM +hemlock/MS +hemmed +hemmer/SM +hemming +hemoglobin/MS +hemolytic +hemophilia/SM +hemophiliac/SM +hemorrhage/GMDS +hemorrhagic +hemorrhoid/MS +hemostat/SM +hemp/MNS +hemstitch/DSMG +hen/MS +hence/S +henceforth +henceforward +henchman/M +henchmen +henge/M +henna/MDSG +henning +henpeck/GSD +henry/M +hep/S +heparin/MS +hepatic/S +hepatitides +hepatitis/M +hepper +heppest +heptagon/SM +heptagonal +heptane/M +heptathlon/S +her +herald/MDSG +heralded/U +heraldic +heraldry/MS +herb/MS +herbaceous +herbage/MS +herbal/S +herbalism +herbalist/MS +herbicidal +herbicide/MS +herbivore/SM +herbivorous/Y +herculean +herd/MDRGZS +herder/M +herdsman/M +herdsmen +here's +here/IS +hereabout/S +hereafter/S +hereby +hereditary +heredity/MS +herein +hereinafter +hereof +hereon +heres/M +heresy/SM +heretic/SM +heretical +hereto +heretofore +hereunder +hereunto +hereupon +herewith +heritable +heritage/MS +heritor/IM +hermaphrodite/SM +hermaphroditic +hermeneutic/S +hermeneutics/M +hermetic/S +hermetical/Y +hermit/MS +hermitage/SM +hermitian +hernia/MS +hernial +herniate/NGXDS +hero/M +heroes +heroic/U +heroically +heroics +heroin/MS +heroine/SM +heroism/SM +heron/SM +herpes/M +herpetologist/SM +herpetology/MS +herring/SM +herringbone/SDGM +herself +hertz/M +hes +hesitance/S +hesitancy/SM +hesitant/U +hesitantly +hesitate/XDRSNG +hesitater/M +hesitating/UY +hesitation/M +heterodox +heterodoxy/MS +heterodyne +heterogamous +heterogamy/M +heterogeneity/SM +heterogeneous/PY +heterogeneousness/M +heterosexual/YMS +heterosexuality/SM +heterostructure +heterozygous +heuristic/SM +heuristically +hew/DRZGS +hewer/M +hex/DSRG +hexachloride/M +hexadecimal/YS +hexafluoride/M +hexagon/SM +hexagonal/Y +hexagram/SM +hexameter/SM +hexer/M +hey +heyday/MS +hf +hgt +hgwy +hi/D +hiatus/SM +hibachi/MS +hibernate/XGNSD +hibernation/M +hibernator/SM +hibiscus/MS +hiccup/MDGS +hick/SM +hickey/SM +hickory/MS +hid/ZDRGJ +hidden/U +hide/S +hideaway/SM +hidebound +hideous/YP +hideousness/SM +hideout/MS +hider/M +hiding/M +hie/S +hieing +hierarchal +hierarchic +hierarchical/Y +hierarchy/SM +hieratic +hieroglyph +hieroglyphic/S +hieroglyphics/M +hieroglyphs +hifalutin +high/PYRT +highball/GSDM +highborn +highboy/MS +highbrow/SM +highchair/SM +highfalutin +highhanded/PY +highhandedness/SM +highish +highland/ZSRM +highlight/GZRDMS +highness/MS +highpoint +highroad/MS +highs +hight +hightail/DGS +highway/MS +highwayman/M +highwaymen +hijack/JZRDGS +hijacker/M +hike/ZGDSR +hiker/M +hilarious/YP +hilariousness/MS +hilarity/MS +hill/GSMDR +hillbilly/MS +hiller/M +hilliness/SM +hillman +hillmen +hillock/SM +hillside/SM +hilltop/MS +hillwalking +hilly/TRP +hilt/MDGS +him/S +himself +hind/RSZ +hinder/GRD +hindered/U +hinderer/M +hindmost +hindquarter/SM +hindrance/SM +hindsight/SM +hinge's +hinge/UDSG +hinger +hint/GZMDRS +hinter/M +hinterland/MS +hip/PSM +hipbone/SM +hipness/S +hipped +hipper +hippest +hippie/MTRS +hipping/M +hippo/MS +hippodrome/MS +hippopotamus/SM +hippy's +hipster/MS +hiragana +hire/AGSD +hireling/SM +hirer/SM +hiring/S +hirsute/P +hirsuteness/MS +his +hiss/DSRMJG +hisser/M +hissing/M +hist/SDG +histamine/SM +histidine/SM +histochemic +histochemical +histochemistry/M +histogram/MS +histological +histologist/MS +histology/SM +historian/MS +historic +historical/PY +historicalness/M +historicism/M +historicist/M +historicity/MS +historiographer/SM +historiography/MS +history/MS +histrionic/S +histrionically +histrionics/M +hit/MS +hitch/UGSD +hitcher/MS +hitchhike/RSDGZ +hither +hitherto +hitless +hittable +hitter/SM +hitting +hive/MGDS +ho/DRYZ +hoar/M +hoard/RDJZSGM +hoarder/M +hoarding/M +hoarfrost/SM +hoariness/MS +hoarse/RTYP +hoarseness/SM +hoary/TPR +hoax/GZMDSR +hoaxer/M +hob/SM +hobbed +hobbing +hobbit +hobble/ZSRDG +hobbler/M +hobby/SM +hobbyhorse/SM +hobbyist/SM +hobgoblin/MS +hobnail/GDMS +hobnob/S +hobnobbed +hobnobbing +hobo/SDMG +hoc +hock/GDRMS +hocker/M +hockey/SM +hockshop/SM +hod/SM +hodge/MS +hodgepodge/SM +hoe/SM +hoecake/SM +hoedown/MS +hoeing +hoer/M +hog/SM +hogan/SM +hogback/MS +hogged +hogger +hogging +hoggish/Y +hogshead/SM +hogtie/SD +hogtying +hogwash/SM +hoist/GRDS +hoister/M +hoke/DSG +hokey/PRT +hokier +hokiest +hokum/MS +hold/NRBSJGZ +holdall/MS +holder/M +holding's +holding/IS +holdout/SM +holdover/SM +holdup/MS +hole/MGDS +holey +holiday/GRDMS +holidaymaker/S +holier/U +holiness/MSU +holistic +holistically +hollandaise +holler/GDS +hollow/RDYTGSP +hollowness/MS +hollowware/M +holly/SM +hollyhock/MS +holmium/MS +holocaust/MS +hologram/SM +holograph/GMD +holographic +holographs +holography/MS +holster/MDSG +holy/SRTP +holystone/MS +homage/MGSRD +homager/M +hombre/SM +homburg/SM +home/DSRMYZG +homebody/MS +homebound +homeboy/S +homebuilder/S +homebuilding +homebuilt +homecoming/MS +homegrown +homeland/SM +homeless/P +homelessness/SM +homelike +homeliness/SM +homely/RPT +homemade +homemake/JRZG +homemaker/M +homemaking/M +homeomorph/M +homeomorphic +homeomorphism/MS +homeopath +homeopathic +homeopaths +homeopathy/MS +homeostases +homeostasis/M +homeostatic +homeowner/S +homeownership +homepage +homer/GDM +homerists +homeroom/MS +homeschooling/S +homesick/P +homesickness/MS +homespun/S +homestead/GZSRDM +homesteader/M +homestretch/SM +hometown/SM +homeward +homework/ZSMR +homeworker/M +homey/PS +homeyness/MS +homicidal/Y +homicide/SM +homier +homiest +homiletic/S +homily/SM +hominess's +homing/M +hominid/MS +hominy/SM +homo/SM +homogamy/M +homogenate/MS +homogeneity/ISM +homogeneous/PY +homogenization/MS +homogenize/DRSGZ +homogenizer/M +homograph/M +homographs +homological +homologous +homologue/M +homology/MS +homomorphic +homomorphism/SM +homonym/SM +homophobia/S +homophobic +homophone/MS +homopolymers +homosexual/YMS +homosexuality/SM +homotopy +homozygous/Y +hon/MDRSZTG +honcho/DSG +hone/SM +honest/RYT +honestly/E +honesty/ESM +honey/GSMD +honeybee/SM +honeycomb/SDMG +honeydew/SM +honeylocust +honeymoon/RDMGZS +honeymooner/M +honeysuckle/MS +hong/M +honk/GZSDRM +honker/M +honky/SM +honor's +honor/ERDBZGS +honorable/PSM +honorableness/SM +honorables/U +honorablies/U +honorably/UE +honorarily +honorarium/SM +honorary/S +honored/U +honoree/S +honorer/EM +honorific/S +honors/A +hooch/MS +hood/MDSG +hooded/P +hoodedness/M +hoodlum/SM +hoodoo/DMGS +hoodwink/SRDG +hoodwinker/M +hooey/SM +hoof/DRMSG +hoofer/M +hoofmark/S +hook/GZDRMS +hookah/M +hookahs +hooked/P +hookedness/M +hooker/M +hookey's +hooks/U +hookup/SM +hookworm/MS +hooky/SRMT +hooligan/SM +hooliganism/SM +hoop/MDRSG +hooper/M +hoopla/SM +hooray/SMDG +hoosegow/MS +hoot/MDRSGZ +hootch's +hootenanny/SM +hooter/M +hooves/M +hop/SMDRG +hope/SM +hoped/U +hopeful/SPY +hopefulness/MS +hopeless/YP +hopelessness/SM +hoper/M +hopped +hopper/MS +hopping/M +hoppled +hopples +hopscotch/MDSG +horde/DSGM +horehound/MS +horizon/MS +horizontal/YS +hormonal/Y +hormone/MS +horn/GDRMS +hornbeam/M +hornblende/MS +horned/P +hornedness/M +hornet/MS +horniness/M +hornless +hornlike +hornpipe/MS +horny/TRP +horologic +horological +horologist/MS +horology/MS +horoscope/MS +horrendous/Y +horrible/SP +horribleness/SM +horribly +horrid/PY +horridness/M +horrific +horrifically +horrify/DSG +horrifying/Y +horror/MS +hors/DSGX +horse's +horse/UGDS +horseback/MS +horsedom +horseflesh/M +horsefly/MS +horsehair/SM +horsehide/SM +horselaugh/M +horselaughs +horseless +horselike +horsely +horseman/M +horsemanship/MS +horsemen +horseplay/SMR +horseplayer/M +horsepower/SM +horseradish/SM +horseshoe/MRSD +horseshoeing +horseshoer/M +horsetail/SM +horsewhip/SM +horsewhipped +horsewhipping +horsewoman/M +horsewomen +horsey +horsier +horsiest +horsing/M +hortatory +horticultural +horticulture/SM +horticulturist/SM +hos/GDS +hosanna/SDG +hose/M +hosepipe +hosier/MS +hosiery/SM +hosp +hospice/MS +hospitable/I +hospitably/I +hospital/MS +hospitality's/I +hospitality/MS +hospitalization/MS +hospitalize/GSD +host/MYDGS +hostage/MS +hostel/SZGMRD +hosteler/M +hostelry/MS +hostess/MDSG +hostile/YS +hostility/SM +hostler/MS +hot/PSY +hotbed/MS +hotblooded +hotbox/MS +hotcake/S +hotchpotch/M +hotel/MS +hotelier/MS +hotelman/M +hotfoot/DGS +hothead/DMS +hotheaded/PY +hotheadedness/SM +hothouse/MGDS +hotness/MS +hotplate/SM +hotpot/M +hotrod +hotshot/S +hotted +hotter +hottest +hotting +hough/M +hound/MRDSG +hounder/M +hounding/M +hour/YMS +hourglass/MS +houri/MS +hourly/S +house's +house/ASDG +houseboat/SM +housebound +houseboy/SM +housebreak/JSRZG +housebreaker/M +housebreaking/M +housebroke +housebroken +housebuilding +houseclean/JDSG +housecleaning/M +housecoat/MS +housefly/MS +houseful/SM +household/ZRMS +householder/M +househusband/S +housekeep/JRGZ +housekeeper/M +housekeeping/M +houselights +housemaid/MS +houseman/M +housemen +housemother/MS +housemoving +houseparent/SM +houseplant/S +houser +housetop/MS +housewares +housewarming/MS +housewife/YM +housewifeliness/M +housewifely/P +housewives +housework/ZSMR +houseworker/M +housing/MS +hove/ZR +hovel/GSMD +hover/GRD +hovercraft/M +hoverer/M +how/SM +howbeit +howdah/M +howdahs +howdy/GSD +however +howitzer/MS +howl/GZSMDR +howler/M +howsoever +hoy/M +hoyden/DMGS +hoydenish +hp +hr +hrs +ht +huarache/SM +hub/MS +hubba +hubbub/SM +hubby/SM +hubcap/SM +hubris/SM +huckleberry/SM +huckster/SGMD +huddle/RSDMG +huddler/M +hue/MDS +huff/SGDM +huffily +huffiness/SM +huffy/TRP +hug/RTS +huge/YP +hugeness/MS +hugged +hugger +hugging/S +huh +huhs +hula/MDSG +hulk/GDMS +hull/MDRGZS +hullabaloo/SM +huller/M +hulling/M +hullo/GSDM +hum/S +human/IPY +humane/IY +humaneness/SM +humaner +humanest +humanism/SM +humanist/SM +humanistic +humanitarian/S +humanitarianism/SM +humanity/ISM +humanization/CSM +humanize/RSDZG +humanized/C +humanizer/M +humanizes/IAC +humanizing/C +humankind/M +humanness/IM +humannesses +humanoid/S +humans +humble/TZGPRSDJ +humbleness/SM +humbly +humbug/MS +humbugged +humbugging +humdinger/MS +humdrum/S +humeral/S +humeri +humerus/M +humid/Y +humidification/MC +humidifier/CM +humidify/RSDCXGNZ +humidistat/M +humidity/MS +humidor/MS +humiliate/SDXNG +humiliating/Y +humiliation/M +humility/MS +hummed +hummer/SM +humming +hummingbird/SM +hummock/MDSG +hummocky +hummus/S +humongous +humor/RDMZGS +humored/U +humorist/MS +humorless/PY +humorlessness/MS +humorous/YP +humorousness/MS +hump/GSMD +humpback/SMD +humph/DG +humphs +humus/SM +hunch/GMSD +hunchback/DSM +hundred/SHRM +hundredfold/S +hundredths +hundredweight/SM +hung/A +hunger/SDMG +hungover +hungrily +hungriness/SM +hungry/RTP +hunk/ZRMS +hunker/DG +hunky/RST +hunt/GZJDRS +hunter/M +hunting/M +huntress/MS +huntsman/M +huntsmen +hurdle/JMZGRSD +hurdler/M +hurl/DRGZJS +hurler/M +hurling/M +hurray/SDG +hurricane/MS +hurried/UY +hurriedness/M +hurry/RSDG +hurt/U +hurter/M +hurtful/PY +hurtfulness/MS +hurting/Y +hurtle/SDG +hurts +husband/GSDRYM +husbander/M +husbandman/M +husbandmen +husbandry/SM +hush/DSG +husk/SGZDRM +husker/M +huskily +huskiness/MS +husking/M +husky/RSPT +hussar/MS +hussy/SM +hustings/M +hustle/RSDZG +hustler/M +hut/MS +hutch/MSDG +hutted +hutting +huzzah/GD +huzzahs +hwy +hyacinth/M +hyacinths +hyaena's +hybrid/MS +hybridism/SM +hybridization/S +hybridize/GSD +hydra/MS +hydrangea/SM +hydrant/SM +hydrate's +hydrate/CSDNGX +hydration/MC +hydraulic/S +hydraulically +hydraulicked +hydraulicking +hydraulics/M +hydrazine/M +hydride/MS +hydro/SM +hydrocarbon/SM +hydrocephali +hydrocephalus/MS +hydrochemistry +hydrochloric +hydrochloride/M +hydrodynamic/S +hydrodynamical +hydrodynamics/M +hydroelectric +hydroelectrically +hydroelectricity/SM +hydrofluoric +hydrofoil/MS +hydrogen/MS +hydrogenate's +hydrogenate/CDSGN +hydrogenation/MC +hydrogenations +hydrogenous +hydrological/Y +hydrologist/MS +hydrology/SM +hydrolysis/M +hydrolyze/GSD +hydrolyzed/U +hydromagnetic +hydromechanics/M +hydrometer/SM +hydrometry/MS +hydrophilic +hydrophobia/SM +hydrophobic +hydrophone/SM +hydroplane/DSGM +hydroponic/S +hydroponics/M +hydrosphere/MS +hydrostatic/S +hydrostatics/M +hydrotherapy/SM +hydrothermal/Y +hydrous +hydroxide/MS +hydroxy +hydroxyl/SM +hydroxylate/N +hydroxyzine/M +hyena/MS +hygiene/MS +hygienic/S +hygienically +hygienics/M +hygienist/MS +hygrometer/SM +hygroscopic +hying +hymen/MS +hymeneal/S +hymn/GSDM +hymnal/SM +hymnbook/S +hype/MZGDSR +hyperactive/S +hyperactivity/SM +hyperbola/MS +hyperbole/MS +hyperbolic +hyperbolically +hyperboloid/SM +hyperboloidal +hypercellularity +hypercritical/Y +hypercube/MS +hyperemia/M +hyperemic +hyperfine +hypergamous/Y +hypergamy/M +hyperglycemia/MS +hyperinflation +hypermarket/SM +hypermedia/S +hyperplane/SM +hyperplasia/M +hypersensitive/P +hypersensitiveness/MS +hypersensitivity/MS +hypersonic +hyperspace/M +hypersphere/M +hypertension/MS +hypertensive/S +hypertext/SM +hyperthyroid +hyperthyroidism/MS +hypertrophy/MSDG +hypervelocity +hyperventilate/XSDGN +hyperventilation/M +hyphen/DMGS +hyphenate/NGXSD +hyphenated/U +hyphenation/M +hypnoses +hypnosis/M +hypnotherapy/SM +hypnotic/S +hypnotically +hypnotism/MS +hypnotist/SM +hypnotize/SDG +hypo/DMSG +hypoactive +hypoallergenic +hypocellularity +hypochondria/MS +hypochondriac/SM +hypocrisy/SM +hypocrite/MS +hypocritical/Y +hypodermic/S +hypoglycemia/SM +hypoglycemic/S +hypophyseal +hypophysectomized +hypotenuse/MS +hypothalami +hypothalamic +hypothalamically +hypothalamus/M +hypothermia/SM +hypotheses +hypothesis/M +hypothesize/ZGRSD +hypothesizer/M +hypothetic +hypothetical/Y +hypothyroid +hypothyroidism/SM +hypoxia/M +hyssop/MS +hysterectomy/MS +hysteresis/M +hysteria/SM +hysteric/SM +hysterical/YU +i +i's +iamb/MS +iambi +iambic/S +iambus/SM +ibex/MS +ibid +ibidem +ibis/SM +ibuprofen/S +ice's +ice/GDSC +iceberg/SM +iceboat/MS +icebound +icebox/MS +icebreaker/SM +icecap/SM +iceman/M +icemen +icepack +icepick/S +ichneumon/M +ichthyologist/MS +ichthyology/MS +icicle/SM +icily +iciness/SM +icing/MS +icky/RT +icon/MS +iconic +iconoclasm/MS +iconoclast/MS +iconoclastic +iconography/MS +icosahedra +icosahedral +icosahedron/M +ictus/SM +icy/RPT +id/MY +idea/SM +ideal/MYS +idealism/MS +idealist/MS +idealistic +idealistically +idealization/MS +idealize/GDRSZ +idealized/U +idealizer/M +idealogical +ideate/SN +ideation/M +idem +idempotent/S +identical/YP +identicalness/M +identifiability +identifiable/U +identifiably +identification/M +identified/U +identifier/M +identify/XZNSRDG +identity/SM +ideogram/MS +ideograph/M +ideographic +ideographs +ideological/Y +ideologist/SM +ideologue/S +ideology/SM +ides +idiocy/MS +idiolect/M +idiom/MS +idiomatic/P +idiomatically +idiopathic +idiosyncrasy/SM +idiosyncratic +idiosyncratically +idiot/MS +idiotic +idiotically +idle/PZTGDSR +idleness/MS +idler/M +idol/MS +idolater/MS +idolatress/S +idolatrous +idolatry/SM +idolization/SM +idolize/ZGDRS +idolized/U +idolizer/M +ids +idyll/MS +idyllic +idyllically +if +iffiness/S +iffy/TPR +ifs +igloo/MS +igneous +ignitable +ignite/ASDG +igniter/M +ignition/MS +ignoble/P +ignobleness/M +ignobly +ignominious/Y +ignominy/MS +ignoramus/SM +ignorance/MS +ignorant/SPY +ignorantness/M +ignore/SRDGB +ignorer/M +iguana/MS +ii +iii +ikon's +ilea +ileitides +ileitis/M +ileum/M +ilia +iliac +ilium/M +ilk/MS +ill/PS +illegal/YS +illegality/MS +illegibility/MS +illegible +illegibly +illegitimacy/SM +illegitimate/SDGY +illiberal/Y +illiberality/SM +illicit/YP +illicitness/MS +illimitable/P +illimitableness/M +illiquid +illiteracy/MS +illiterate/PSY +illiterateness/M +illness/MS +illogic/M +illogical/PY +illogicality/SM +illogicalness/M +illume/DG +illuminate/XSDVNG +illuminating/U +illuminatingly +illumination/M +illumine/BGSD +illus/V +illusion's +illusion/ES +illusionary +illusionist/MS +illusive/PY +illusiveness/M +illusoriness/M +illusory/P +illustrate/VGNSDX +illustrated/U +illustration/M +illustrative/Y +illustrator/SM +illustrious/PY +illustriousness/SM +illy +image/DSGM +imagery/MS +imaginable/U +imaginableness +imaginably/U +imaginariness/M +imaginary/PS +imagination/MS +imaginative/UY +imaginativeness/M +imagine/RSDJBG +imagined/U +imaginer/M +imago/M +imagoes +imam/MS +imbalance/SDM +imbecile/YMS +imbecilic +imbecility/MS +imbibe/ZRSDG +imbiber/M +imbrication/SM +imbroglio/MS +imbruing +imbue/GDS +imitable/I +imitate/SDVNGX +imitation/M +imitative/YP +imitativeness/MS +imitator/SM +immaculate/YP +immaculateness/SM +immanence/S +immanency/MS +immanent/Y +immaterial/PY +immateriality/MS +immaterialness/MS +immature/SPY +immatureness/M +immaturity/MS +immeasurable/P +immeasurableness/M +immeasurably +immediacy/MS +immediate/YP +immediateness/SM +immemorial/Y +immense/PRTY +immenseness/M +immensity/MS +immerse/RSDXNG +immersible +immersion/M +immigrant/SM +immigrate/NGSDX +immigration/M +imminence/SM +imminent/YP +imminentness/M +immobile +immobility/MS +immobilization/MS +immobilize/DSRG +immoderate/NYP +immoderateness/M +immoderation/M +immodest/Y +immodesty/SM +immolate/SDNGX +immolation/M +immoral/Y +immorality/MS +immortal/SY +immortality/SM +immortalize/GDS +immortalized/U +immovability/SM +immovable/PS +immovableness/M +immovably +immune/S +immunity/SM +immunization/MS +immunize/GSD +immunoassay/M +immunodeficiency/S +immunodeficient +immunologic +immunological/Y +immunologist/SM +immunology/MS +immure/GSD +immutability/MS +immutable/P +immutableness/M +immutably +imp/SGMDRY +impact/VGMRDS +impaction/SM +impactor/SM +impair/LGRDS +impaired/U +impairer/M +impairment/SM +impala/MS +impale/GLRSD +impalement/SM +impaler/M +impalpable +impalpably +impanel/DGS +impart/GDS +impartation/M +impartial/Y +impartiality/SM +impassable/P +impassableness/M +impassably +impasse/SXBMVN +impassibility/SM +impassible +impassibly +impassion/DG +impassioned/U +impassive/YP +impassiveness/MS +impassivity/MS +impasto/SM +impatience/SM +impatiens/M +impatient/Y +impeach/DRSZGLB +impeachable/U +impeacher/M +impeachment/MS +impeccability/SM +impeccable/S +impeccably +impecunious/PY +impecuniousness/MS +imped/GRD +impedance/MS +impede/S +impeded/U +impeder/M +impediment/SM +impedimenta +impel/S +impelled +impeller/MS +impelling +impend/DGS +impenetrability/MS +impenetrable/P +impenetrableness/M +impenetrably +impenitence/MS +impenitent/YS +imperative/PSY +imperativeness/M +imperceivable +imperceptibility/MS +imperceptible +imperceptibly +imperceptive +imperf +imperfect/YSVP +imperfectability +imperfection/MS +imperfectness/SM +imperial/YS +imperialism/MS +imperialist/SM +imperialistic +imperialistically +imperil/GSLD +imperilment/SM +imperious/YP +imperiousness/MS +imperishable/SP +imperishableness/M +imperishably +impermanence/MS +impermanent/Y +impermeability/SM +impermeable/P +impermeableness/M +impermeably +impermissible +impersonal/Y +impersonality/M +impersonalized +impersonate/XGNDS +impersonation/M +impersonator/SM +impertinence/SM +impertinent/YS +imperturbability/SM +imperturbable +imperturbably +impervious/PY +imperviousness/M +impetigo/MS +impetuosity/MS +impetuous/YP +impetuousness/MS +impetus/MS +impiety/MS +imping/GD +impinge/LS +impingement/MS +impious/PY +impiousness/SM +impish/YP +impishness/MS +implacability/SM +implacable/P +implacableness/M +implacably +implant/BGSDR +implantation/SM +implanter/M +implausibility/MS +implausible +implausibly +implement/SMRDGZB +implementability +implementable/U +implementation's +implementation/A +implementations +implemented/AU +implementer/M +implementing/A +implementor/MS +implicant/SM +implicate/VGSD +implication/M +implicative/PY +implicit/YP +implicitness/SM +implied/Y +implode/GSD +implore/GSD +imploring/Y +implosion/SM +implosive/S +imply/GNSDX +impolite/YP +impoliteness/MS +impolitic/PY +impoliticness/M +imponderable/PS +imponderableness/M +import/SZGBRD +importance/SM +important/Y +importation/MS +importer/M +importing/A +importunate/PYGDS +importunateness/M +importune/SRDZYG +importuner/M +importunity/SM +imposable +impose/ASDG +imposer/SM +imposing/U +imposingly +imposition/SM +impossibility/SM +impossible/PS +impossibleness/M +impossibly +impost/SGMD +imposter's +impostor/SM +imposture/SM +impotence/MS +impotency/S +impotent/SY +impound/GDS +impoundments +impoverish/LGDRS +impoverisher/M +impoverishment/SM +impracticable/P +impracticableness/M +impracticably +impractical/PY +impracticality/SM +impracticalness/M +imprecate/NGXSD +imprecation/M +imprecise/PYXN +impreciseness/MS +imprecision/M +impregnability/MS +impregnable/P +impregnableness/M +impregnably +impregnate/DSXNG +impregnation/M +impresario/SM +impress/DRSGVL +impressed/U +impresser/M +impressibility/MS +impressible +impression/BMS +impressionability/SM +impressionable/P +impressionableness/M +impressionism/SM +impressionist/MS +impressionistic +impressive/YP +impressiveness/MS +impressment/M +imprimatur/SM +imprint/SZDRGM +imprinter/M +imprinting/M +imprison/GLDS +imprisonment/MS +improbability/MS +improbable/P +improbableness/M +improbably +impromptu/S +improper/PY +improperness/M +impropitious +impropriety/SM +improve/SRDGBL +improved/U +improvement/MS +improver/M +improvidence/SM +improvident/Y +improvisation/MS +improvisational +improvisatory +improvise/RSDZG +improviser/M +imprudence/SM +imprudent/Y +impudence/MS +impudent/Y +impugn/SRDZGB +impugner/M +impulse/XMVGNSD +impulsion/M +impulsive/YP +impulsiveness/MS +impunity/SM +impure/RPTY +impureness/M +impurity/MS +imputation/SM +impute/SDBG +in/AS +inaction +inactive +inadequate/S +inadvertence/MS +inadvertent/Y +inalienability/MS +inalienably +inalterable/P +inalterableness/M +inamorata/MS +inane/SRPYT +inanimate/P +inanimateness/S +inanity/MS +inappeasable +inappropriate/P +inarticulate/P +inasmuch +inaugural/S +inaugurate/XSDNG +inauguration/M +inauthenticity +inbound/G +inbred/S +inbreed/JG +inc/T +incalculableness/M +incalculably +incandescence/SM +incandescent/YS +incant +incantation/SM +incantatory +incapable/S +incapacitate/GNSD +incapacitation/M +incarcerate/XGNDS +incarceration/M +incarnadine/GDS +incarnate/AGSDNX +incarnation/AM +incendiary/S +incense/MGDS +incentive/ESM +incentively +incept/DGVS +inception/MS +inceptive/Y +inceptor/M +incessant/Y +incest/SM +incestuous/PY +incestuousness/MS +inch/GMDS +inchoate/DSG +inchworm/MS +incidence/MS +incident/SM +incidental/YS +incinerate/XNGSD +incineration/M +incinerator/SM +incipience/SM +incipiency/M +incipient/Y +incise/SDVGNX +incision/M +incisive/YP +incisiveness/MS +incisor/MS +incite/RZL +incitement/MS +inciter/M +incl +inclination/ESM +incline/EGSD +incliner/M +inclining/M +include/GDS +inclusion/MS +inclusive/PY +inclusiveness/MS +incognito/S +incoherency/M +income/M +incommode/DG +incommunicado +incomparable +incompetent/MS +incomplete/P +inconceivability/MS +inconceivable/P +inconceivableness/M +incondensable +incongruousness/S +inconsiderable/P +inconsiderableness/M +inconsistence +inconsolable/P +inconsolableness/M +inconsolably +incontestability/SM +incontestably +incontrovertibly +inconvenience/DG +inconvertibility +inconvertible +incorporable +incorporate/GASDXN +incorporated/UE +incorrect/P +incorrigibility/MS +incorrigible/SP +incorrigibleness/M +incorrigibly +incorruptible/S +incorruptibly +increase/JB +increaser/M +increasing/Y +incredible/P +incredibleness/M +increment/DMGS +incremental/Y +incrementation +incriminate/XNGSD +incrimination/M +incriminatory +incrustation/SM +incubate/XNGVDS +incubation/M +incubator/MS +incubus/MS +inculcate/SDGNX +inculcation/M +inculpate/SDG +incumbency/MS +incumbent/S +incunabula +incunabulum +incurable/S +incurious +incursion/SM +ind +indebted/P +indebtedness/SM +indefatigable/P +indefatigableness/M +indefatigably +indefeasible +indefeasibly +indefinable/PS +indefinableness/M +indefinite/S +indelible +indelibly +indemnification/M +indemnify/NXSDG +indemnity/SM +indent/R +indentation/SM +indented/U +indenter/M +indention/SM +indenture/DG +indescribable/PS +indescribableness/M +indescribably +indestructible/P +indestructibleness/M +indestructibly +indeterminably +indeterminacy/MS +indeterminism +index/MRDZGB +indexation/S +indexer/M +indicant/MS +indicate/DSNGVX +indication/M +indicative/SY +indicator/MS +indices's +indict/SGLBDR +indicter/M +indictment/SM +indifference +indigence/MS +indigenous/YP +indigenousness/M +indigent/SY +indigestible/S +indignant/Y +indignation/MS +indigo/SM +indirect/PG +indiscreet/P +indiscriminate/PY +indiscriminateness/M +indispensability/MS +indispensable/SP +indispensableness/M +indispensably +indisputable/P +indisputableness/M +indissoluble/P +indissolubleness/M +indissolubly +indistinguishable/P +indistinguishableness/M +indite/SDG +indium/SM +individual/YMS +individualism/MS +individualist/MS +individualistic +individualistically +individuality/MS +individualization/SM +individualize/DRSGZ +individualized/U +individualizer/M +individualizes/U +individualizing/Y +individuate/DSXGN +individuation/M +indivisible/SP +indivisibleness/M +indivisibly +indoctrinate/GNXSD +indoctrination/M +indoctrinator/SM +indolence/SM +indolent/Y +indomitable/P +indomitableness/M +indomitably +indoor +indubitable/P +indubitableness/M +indubitably +induce/ZGLSRD +inducement/MS +inducer/M +inducible +induct/GV +inductance/MS +inductee/SM +induction/SM +inductive/PY +inductiveness/M +inductor/MS +indulge/GDRS +indulgence/SDGM +indulgent/Y +indulger/M +industrial/SY +industrialism/MS +industrialist/MS +industrialization/MS +industrialize/SDG +industrialized/U +industrious/YP +industriousness/SM +industry/SM +inebriate/NGSDX +inebriation/M +inedible +ineducable +ineffability/MS +ineffable/P +ineffableness/M +ineffably +inelastic +ineligibly +ineluctable +ineluctably +inept/YP +ineptitude/SM +ineptness/MS +inequivalent +inerrant +inert/SPY +inertia/SM +inertial/Y +inertness/MS +inescapably +inestimably +inevitability/MS +inevitable/P +inevitableness/M +inevitably +inexact/P +inexhaustible/P +inexhaustibleness/M +inexhaustibly +inexorability/M +inexorable/P +inexorableness/M +inexorably +inexpedience/M +inexplicable/P +inexplicableness/M +inexplicably +inexplicit +inexpressibility/M +inexpressible/PS +inexpressibleness/M +inextricably +inf/ZT +infamous +infamy/SM +infancy/M +infant/MS +infanticide/MS +infantile +infantry/SM +infantryman/M +infantrymen +infarct/SM +infarction/SM +infatuate/XNGSD +infatuation/M +infauna +infect/ESGDA +infected/U +infecter +infection/EASM +infectious/PY +infectiousness/MS +infective +infer/B +inference/GMSR +inferential/Y +inferior/SMY +inferiority/MS +infernal/Y +inferno/MS +inferred +inferring +infertile +infest/GSDR +infestation/MS +infester/M +infidel/SM +infighting/M +infill/MG +infiltrate/V +infiltrator/MS +infinite/V +infinitesimal/SY +infinitival +infinitive/YMS +infinitude/MS +infinitum +infinity/SM +infirmary/SM +infirmity/SM +infix/M +inflammable/P +inflammableness/M +inflammation/MS +inflammatory +inflatable/MS +inflate/NGBDRSX +inflater/M +inflation/ESM +inflationary +inflect/GVDS +inflection/SM +inflectional/Y +inflexible/P +inflexibleness/M +inflexion/SM +inflict/DRSGV +inflicter/M +infliction/SM +inflow/M +influence/SRDGM +influenced/U +influencer/M +influent +influential/SY +influenza/MS +info/SM +infomercial/S +informatics +information/ES +informational +informative/UY +informativeness/S +informatory +informed/U +informer/M +infotainment/S +infra +infrared/SM +infrasonic +infrastructural +infrastructure/MS +infrequence/S +infringe/LR +infringement/SM +infringer/M +infuriate/GNYSD +infuriating/Y +infuriation/M +infuse/RZ +infuser/M +infusible/P +infusibleness/M +ingenious/YP +ingeniousness/MS +ingenuity/SM +ingenuous/EY +ingenuousness/MS +ingest/DGVS +ingestible +ingestion/SM +inglenook/MS +ingoing +ingot/SMDG +ingrained/Y +ingrate/M +ingratiate/DSGNX +ingratiating/Y +ingratiation/M +ingredient/SM +ingress/MS +ingression/M +ingrown/P +inguinal +ingénue/S +inhabit/R +inhabitable/U +inhabitance +inhabited/U +inhabiter/M +inhalant/S +inhalation/SM +inhalator/SM +inhale/Z +inhere/DG +inherent/Y +inherit/BDSG +inheritable/P +inheritableness/M +inheritance/EMS +inherited/E +inheriting/E +inheritor/S +inheritress/MS +inheritrix/MS +inherits/E +inhibit/DVGS +inhibited/U +inhibiter's +inhibition/MS +inhibitor/MS +inhibitory +inhomogeneous +inhospitable/P +inhospitableness/M +inhospitality +inimical/Y +inimitable/P +inimitableness/M +inimitably +inion +iniquitous/PY +iniquitousness/M +iniquity/MS +initial/GSPRDY +initialer/M +initialization's +initialization/A +initializations +initialize/ASDG +initialized/U +initializer/S +initiate/UD +initiates +initiating +initiation/SM +initiative/SM +initiator/MS +initiatory +inject/GVSDB +injectable/U +injection/MS +injector/SM +injunctive +injure/SRDZG +injured/U +injurer/M +injurious/YP +injuriousness/M +ink/ZDRJ +inkblot/SM +inker/M +inkiness/MS +inkling/SM +inkstand/SM +inkwell/SM +inky/TP +inland +inlander/M +inlay/RG +inletting +inly/G +inmost +inn/ZGDRSJ +innards +innate/YP +innateness/SM +inner/Y +innermost/S +innersole/S +innerspring +innervate/GNSDX +innervation/M +inning/M +innkeeper/MS +innocence/SM +innocent/SYRT +innocuous/PY +innocuousness/MS +innovate/SDVNGX +innovation/M +innovative/P +innovator/MS +innovatory +innuendo/MDGS +innumerability/M +innumerable/P +innumerableness/M +innumerably +innumerate +inoculate/ASDG +inoculation/MS +inoculative +inoffensive/P +inopportune/P +inopportuneness/M +inordinate/PY +inordinateness/M +inorganic +inpatient +input/MRDG +inquire/ZR +inquirer/M +inquiring/Y +inquiry/MS +inquisition/MS +inquisitional +inquisitive/YP +inquisitiveness/MS +inquisitor/MS +inquisitorial/Y +inrush/M +ins +insalubrious +insanitary +insatiability/MS +insatiable/P +insatiableness/M +insatiably +inscribe/Z +inscription/SM +inscrutability/SM +inscrutable/P +inscrutableness/SM +inscrutably +inseam +insecticidal +insecticide/MS +insectivore/SM +insectivorous +insecure/P +insecureness/M +inseminate/NGXSD +insemination/M +insensate/P +insensateness/M +insensible/P +insentient +inseparable/S +insert/ADSG +inserter/M +insertion/AMS +insetting +inshore +inside/Z +insider/M +insidious/YP +insidiousness/MS +insightful/Y +insigne's +insignia/SM +insignificant +insinuate/VNGXSD +insinuating/Y +insinuation/M +insinuator/SM +insipid/Y +insipidity/MS +insist/SGD +insistence/SM +insistent/Y +insisting/Y +insociable +insofar +insole/M +insolence/SM +insolent/YS +insoluble/P +insolubleness/M +insolubly +insomnia/MS +insomniac/S +insomuch +insouciance/SM +insouciant/Y +inspect/AGSD +inspection/SM +inspective +inspector/SM +inspectorate/MS +inspiration/MS +inspirational/Y +inspire/R +inspired/U +inspirer/M +inspiring/U +inspirit/DG +inst/B +install/ADRSG +installable +installation/SM +installer/MS +installment/MS +instance/GD +instant/SRYMP +instantaneous/PY +instantaneousness/M +instantiate/SDXNG +instantiated/U +instantiation/M +instate/AGSD +instead +instigate/XSDVGN +instigation/M +instigator/SM +instillation/SM +instinct/VMS +instinctive/Y +instinctual +institute/ZXVGNSRD +instituter/M +institutes/M +institution/AM +institutional/Y +institutionalism/M +institutionalist/M +institutionalization/SM +institutionalize/GDS +institutor's +instr +instruct/DSVG +instructed/U +instruction/MS +instructional +instructive/PY +instructiveness/M +instructor/MS +instrument/GMDS +instrumental/SY +instrumentalist/MS +instrumentality/SM +instrumentation/SM +insubordinate +insubstantial +insufferable +insufferably +insular/YS +insularity/MS +insulate/DSXNG +insulated/U +insulation/M +insulator/MS +insulin/MS +insult/DRSG +insulter/M +insulting/Y +insuperable +insuperably +insupportable/P +insupportableness/M +insurance's/A +insurance/MS +insure/BZGS +insured/S +insurer/M +insurgence/SM +insurgency/MS +insurgent/MS +insurmountably +insurrection/SM +insurrectionist/SM +int/ZR +intact/P +intactness/M +intaglio/GMDS +intake/M +intangible/M +integer/MS +integrability/M +integrable +integral/SYM +integrand/MS +integrate/AGNXEDS +integration/EMA +integrative/E +integrator/MS +integrity/SM +integument/SM +intellect/MVS +intellective/Y +intellectual/YPS +intellectualism/MS +intellectuality/M +intellectualize/GSD +intellectualness/M +intelligence/MSR +intelligencer/M +intelligent/UY +intelligentsia/MS +intelligibilities +intelligibility/UM +intelligible/PU +intelligibleness/MU +intelligibly/U +intemperate/P +intendant/MS +intended/SYP +intendedness/M +intender/M +intensification/M +intensifier/M +intensify/GXNZRSD +intensional/Y +intensive/PSY +intensiveness/MS +intent/YP +intention/SDM +intentional/UY +intentionality/M +intentness/SM +inter/ESTL +interact/VGDS +interaction/MS +interactive/PY +interactivity +interaxial +interbank +interbred +interbreed/GS +intercalate/GNVDS +intercalation/M +intercase +intercaste +intercede/SRDG +interceder/M +intercensal +intercept/DGS +interception/MS +interceptor/MS +intercession/MS +intercessor/SM +intercessory +interchange/DSRGJ +interchangeability/M +interchangeable/P +interchangeableness/M +interchangeably +interchanger/M +intercity +interclass +intercohort +intercollegiate +intercom/SM +intercommunicate/SDXNG +intercommunication/M +interconnect/GDS +interconnected/P +interconnectedness/M +interconnection/SM +interconnectivity +intercontinental +interconversion/M +intercorrelated +intercourse/SM +interdenominational +interdepartmental/Y +interdependence/MS +interdependency/SM +interdependent/Y +interdict/MDVGS +interdiction/MS +interdisciplinary +interest/GEMDS +interested/UYE +interesting/YP +interestingly/U +interestingness/M +interface/SRDGM +interfacing/M +interfaith +interfere/SRDG +interference/MS +interferer/M +interfering/Y +interferometer/SM +interferometric +interferometry/M +interferon/MS +interfile/GSD +intergalactic +intergeneration/M +intergenerational +interglacial +intergovernmental +intergroup +interim/S +interindex +interindustry +interior/SMY +interj +interject/GDS +interjection/MS +interjectional +interlace/GSD +interlard/SGD +interlayer/G +interleave/SDG +interleukin/S +interlibrary +interline/JGSD +interlinear/S +interlingua/M +interlingual +interlining/M +interlink/GDS +interlisp/M +interlobular +interlock/RDSG +interlocker/M +interlocutor/MS +interlocutory +interlope/GZSRD +interloper/M +interlude/MSDG +intermarriage/MS +intermarry/GDS +intermediary/MS +intermediate/YMNGSDP +intermediateness/M +intermediation/M +interment/SME +intermeshed +intermetrics +intermezzi +intermezzo/SM +interminably +intermingle/DSG +intermission/MS +intermittent/Y +intermix/GSRD +intermodule +intermolecular/Y +intern/L +internal/SY +internalization/SM +internalize/GDS +international/YS +internationalism/SM +internationalist/SM +internationality/M +internationalization/MS +internationalize/DSG +interne's +internecine +internee/SM +internetwork +internist/SM +internment/SM +internship/MS +internuclear +interocular +interoffice +interoperability +interpenetrates +interpersonal/Y +interplanetary +interplay/GSMD +interpol +interpolate/XGNVBDS +interpolation/M +interpose/GSRD +interposer/M +interposition/MS +interpret/AGSD +interpretable/U +interpretation/MSA +interpretative/Y +interpreted/U +interpreter/SM +interpretive/Y +interpretor/S +interprocess +interprocessor +interquartile +interracial +interred/E +interregional +interregnum/MS +interrelate/GNDSX +interrelated/PY +interrelatedness/M +interrelation/M +interrelationship/SM +interring/E +interrogate/DSXGNV +interrogation/M +interrogative/SY +interrogator/SM +interrogatory/S +interrupt/VGZRDS +interrupted/U +interrupter/M +interruptibility +interruptible +interruption/MS +interscholastic +intersect/GDS +intersection/MS +intersession/MS +interspecies +intersperse/GNDSX +interspersion/M +interstage +interstate/S +interstellar +interstice/SM +interstitial/SY +intersurvey +intertask +intertwine/GSD +interurban/S +interval/MS +intervene/GSRD +intervener/M +intervenor/M +intervention/MS +interventionism/MS +interventionist/S +interview/AMD +interviewed/U +interviewee/SM +interviewer/SM +interviewing +interviews +intervocalic +interweave/GS +interwove +interwoven +intestacy/SM +intestinal/Y +intestine/SM +inti +intifada +intimacy/SM +intimal +intimate/XYNGPDRS +intimateness/M +intimater/M +intimation/M +intimidate/SDXNG +intimidating/Y +intimidation/M +into +intolerable/P +intolerableness/M +intolerant/PS +intonate/NX +intonation/M +intoxicant/MS +intoxicate/DSGNX +intoxicated/Y +intoxication/M +intra +intracellular +intracity +intraclass +intracohort +intractability/M +intractable/P +intractableness/M +intradepartmental +intrafamily +intragenerational +intraindustry +intraline +intrametropolitan +intramural/Y +intramuscular/Y +intranasal +intransigence/MS +intransigent/YS +intransitive/S +intraoffice +intraprocess +intrapulmonary +intraregional +intrasectoral +intrastate +intratissue +intrauterine +intravenous/YS +intrepid/YP +intrepidity/SM +intrepidness/M +intricacy/SM +intricate/PY +intricateness/M +intrigue/DRSZG +intriguer/M +intriguing/Y +intrinsic/S +intrinsically +intro/S +introduce/ADSG +introducer/M +introduction/ASM +introductory +introit/SM +introject/SD +introspect/SGVD +introspection/MS +introspective/YP +introspectiveness/M +introversion/SM +introvert/SMDG +intrude/ZGDSR +intruder/M +intrusion/SM +intrusive/SYP +intrusiveness/MS +intubate/NGDS +intubation/M +intuit/GVDSB +intuitionist/M +intuitive/YP +intuitiveness/MS +inundate/SXNG +inundation/M +inure/GDS +invade/ZSRDG +invader/M +invalid/GSDM +invalidism/MS +invariable/P +invariant/M +invasion/SM +invasive/P +invective/PSMY +invectiveness/M +inveigh/DRG +inveigher/M +inveighs +inveigle/DRSZG +inveigler/M +invent/ADGS +invented/U +invention/ASM +inventive/YP +inventiveness/MS +inventor/MS +inventory/SDMG +inverse/YV +invert/ZSGDR +inverter/M +invertible +invest/ADSLG +investigate/XDSNGV +investigation/MA +investigator/MS +investigatory +investiture/SM +investment's/A +investment/ESA +investor/SM +inveteracy/MS +inveterate/Y +inviability +invidious/YP +invidiousness/MS +invigilate/GD +invigilator/SM +invigorate/ANGSD +invigorating/Y +invigoration/AM +invigorations +invincibility/SM +invincible/P +invincibleness/M +invincibly +inviolability/MS +inviolably +inviolate/YP +inviolateness/M +inviscid +invisible/S +invisibleness/M +invitation/MS +invitational/S +invite/SRDG +invited/U +invitee/S +inviter/M +inviting/Y +invocable +invocate +invoke/GSRDBZ +invoked/A +invoker/M +invokes/A +involuntariness/S +involuntary/P +involute/XYN +involution/M +involutorial +involve/GDSRL +involved/U +involvedly +involvement/SM +involver/M +invulnerability/M +invulnerableness/M +inward/PY +inwardness/M +ioctl +iodate/MGND +iodation/M +iodide/MS +iodinate/DNG +iodine/MS +iodize/GSD +ion's/I +ion/SMU +ionic/S +ionization's +ionization/SU +ionize/GNSRDJXZ +ionized/UC +ionizer's +ionizer/US +ionizes/U +ionizing/U +ionosphere/SM +ionospheric +iota/SM +ipecac/MS +ipso +irascibility/SM +irascible +irascibly +irate/RPYT +irateness/S +ire/MGDS +ireful +irenic/S +irides/M +iridescence/SM +iridescent/Y +iridium/MS +irids +iris/GDSM +irk/GDS +irksome/YP +irksomeness/SM +iron/DRMPSGJ +ironclad/S +ironer/M +ironic +ironical/YP +ironicalness/M +ironing/M +ironmonger/M +ironmongery/M +ironside/MS +ironstone/MS +ironware/SM +ironwood/SM +ironwork/MRS +ironworker/M +irony/SM +irradiate/XSDVNG +irradiation/M +irrational/YSP +irrationality/MS +irrationalness/M +irreclaimable +irreconcilability/MS +irreconcilable/PS +irreconcilableness/M +irreconcilably +irrecoverable/P +irrecoverableness/M +irrecoverably +irredeemable/S +irredeemably +irredentism/M +irredentist/M +irreducibility/M +irreducible +irreducibly +irreflexive +irrefutable +irrefutably +irregardless +irregular/YS +irregularity/SM +irrelevance/SM +irrelevancy/MS +irrelevant/Y +irreligious +irremediable/P +irremediableness/M +irremediably +irremovable +irreparable/P +irreparableness/M +irreparably +irreplaceable/P +irrepressible +irrepressibly +irreproachable/P +irreproachableness/M +irreproachably +irreproducibility +irreproducible +irresistibility/M +irresistible/P +irresistibleness/M +irresistibly +irresolute/PNXY +irresoluteness/SM +irresolution/M +irresolvable +irrespective/Y +irresponsibility/SM +irresponsible/PS +irresponsibleness/M +irresponsibly +irretrievable +irretrievably +irreverence/MS +irreverent/Y +irreversible +irreversibly +irrevocable/P +irrevocableness/M +irrevocably +irrigable +irrigate/DSXNG +irrigation/M +irritability/MS +irritable/P +irritableness/M +irritably +irritant/S +irritate/DSXNGV +irritated/Y +irritating/Y +irritation/M +irrupt/GVSD +irruption/SM +is +isinglass/MS +isl/GD +island/GZMRDS +islander/M +isle/MS +islet/SM +ism/MCS +isn't +isobar/MS +isobaric +isochronal/Y +isochronous/Y +isocline/M +isocyanate/M +isodine +isolate/SDXNG +isolation/M +isolationism/SM +isolationist/SM +isolationistic +isolator/MS +isomer/SM +isomeric +isomerism/SM +isometric/S +isometrically +isometrics/M +isomorph/M +isomorphic +isomorphically +isomorphism/MS +isoperimetrical +isopleth/M +isopleths +isosceles +isostatic +isotherm/MS +isothermal/Y +isotonic +isotope/SM +isotopic +isotropic +isotropically +isotropy/M +ispell/M +issuable +issuance/MS +issuant +issue/GMZDSR +issued/A +issuer/AMS +issues/A +issuing/A +isthmian/S +isthmus/SM +it'd +it'll +it/MUS +ital +italic/S +italicization/MS +italicize/GSD +italicized/U +itch/GMDS +itchiness/MS +itchy/RTP +item/MDSG +itemization/SM +itemize/GZDRS +itemized/U +itemizer/M +itemizes/A +iterate/ASDXVGN +iteration/M +iterative/YA +iterator/MS +itinerant/SY +itinerary/MS +its +itself +iv/M +ivory/SM +ivy/MDS +ix +j's +j/F +jab/SM +jabbed +jabber/JRDSZG +jabberer/M +jabbing +jabot/MS +jacaranda/MS +jack/GDRMS +jackal/SM +jackass/SM +jackboot/DMS +jackdaw/SM +jacket/GSMD +jacketed/U +jackhammer/MDGS +jackknife/MGSD +jackknives +jackpot/MS +jackrabbit/DGS +jackstraw/MS +jacquard/MS +jacuzzi +jade/MGDS +jaded/PY +jadedness/SM +jadeite/SM +jag/S +jagged/RYTP +jaggedness/SM +jaggers +jagging +jaguar/MS +jail/GZSMDR +jailbird/MS +jailbreak/SM +jailer/M +jalapeño/S +jalopy/SM +jalousie/MS +jam/SM +jamb/DMGS +jambalaya/MS +jamboree/MS +jammed/U +jamming/U +jangle/RSDGZ +jangler/M +jangly +janissary/MS +janitor/SM +janitorial +japan/SM +japanned +japanner +japanning +jape/DSMG +jar/MS +jardinière/MS +jarful/S +jargon/SGDM +jarred +jarring/SY +jasmine/MS +jasper/MS +jato/SM +jaundice/DSMG +jaundiced/U +jaunt/MDGS +jauntily +jauntiness/MS +jaunty/SRTP +javelin/SDMG +jaw/SMDG +jawbone/SDMG +jawbreaker/SM +jawline +jay/SM +jaybird/SM +jaywalk/JSRDZG +jaywalker/M +jazz/MGDS +jazziness/M +jazzmen +jazzy/PTR +jct +jealous/PY +jealousness/M +jealousy/MS +jean/MS +jeep/GZSMD +jeer/SJDRMG +jeerer/M +jeering/Y +jeez +jehad's +jejuna +jejune/PY +jejuneness/M +jejunum/M +jell/GSD +jello's +jelly/SDMG +jellybean/SM +jellyfish/MS +jellying/M +jellylike +jellyroll/S +jemmy/M +jennet/SM +jenny/SM +jeopard +jeopardize/GSD +jeopardy/MS +jeremiad/SM +jerk/GSDRJ +jerker/M +jerkily +jerkin/SM +jerkiness/SM +jerkwater/S +jerky/RSTP +jerry/M +jerrybuilt +jersey/MS +jess/M +jessamine's +jest/DRSGZM +jester/M +jesting/Y +jet/MS +jetliner/MS +jetport/SM +jetsam/MS +jetted/M +jetting/M +jettison/DSG +jetty/RSDGMT +jewel/GZMRDS +jeweler/M +jewelery/S +jewellery's +jewelry/MS +jg/M +jib/MDSG +jibbed +jibbing +jibe/S +jiff/S +jiffy/SM +jig/MS +jigged +jigger/SDMG +jigging/M +jiggle/SDG +jiggly/TR +jigsaw/GSDM +jihad/SM +jilt/DRGS +jilter/M +jimmy/GSDM +jimsonweed/S +jingle/RSDG +jingler/M +jingly/TR +jingo/M +jingoism/SM +jingoist/SM +jingoistic +jinn/MS +jinni's +jinrikisha/SM +jinx/GMDS +jitney/MS +jitter/S +jitterbug/SM +jitterbugged +jitterbugger +jitterbugging +jittery/TR +jiujitsu's +jive/MGDS +job/SM +jobbed +jobber/MS +jobbery/M +jobbing/M +jobholder/SM +jobless/P +joblessness/MS +jock/GDMS +jockey/SGMD +jockstrap/MS +jocose/YP +jocoseness/MS +jocosity/SM +jocular/Y +jocularity/SM +jocund/Y +jocundity/SM +jodhpurs +joey/M +jog/S +jogged +jogger/SM +jogging/S +joggle/SRDG +joggler/M +john/SM +johnny/SM +johnnycake/SM +join/ADGFS +joined/U +joiner/FSM +joinery/MS +joint's +joint/EGDYPS +jointed/EYP +jointedness/ME +jointer/M +jointly/F +jointures +joist/GMDS +joke/MZDSRG +joker/M +jokey +jokier +jokiest +jokily +joking/Y +jollification/MS +jollily +jolliness/SM +jollity/MS +jolly/TSRDGP +jolt/DRGZS +jolter/M +jonquil/MS +josh/DSRGZ +josher/M +joss/M +jostle/SDG +jot/S +jotted +jotter/SM +jotting/SM +joule/SM +jounce/SDG +jouncy/RT +journal/GSDM +journalese/MS +journalism/SM +journalist/SM +journalistic +journalize/DRSGZ +journalized/U +journalizer/M +journey/DRMZSGJ +journeyer/M +journeyman/M +journeymen +joust/ZSMRDG +jouster/M +jovial/Y +joviality/SM +jowl/SMD +jowly/TR +joy/MDSG +joyful/PY +joyfuller +joyfullest +joyfulness/SM +joyless/PY +joylessness/MS +joyous/YP +joyousness/MS +joyridden +joyride/SRZMGJ +joyrode +joystick/S +jubilant/Y +jubilate/XNGDS +jubilation/M +jubilee/SM +juddered +juddering +judge's +judge/AGDS +judger/M +judgeship/SM +judgment/MS +judgmental/Y +judicable +judicatory/S +judicature/MS +judicial/Y +judiciary/S +judicious/IYP +judiciousness/SMI +judo/MS +jug/MS +jugate/F +jugful/SM +jugged +juggernaut/SM +jugging +juggle/RSDGZ +juggler/M +jugglery/MS +jugular/S +juice/GMZDSR +juicer/M +juicily +juiciness/MS +juicy/TRP +jujitsu/MS +juju/M +jujube/SM +jujutsu's +juke/GS +jukebox/SM +julep/SM +julienne/GSD +jumble/GSD +jumbo/MS +jump/GZDRS +jumper/M +jumpily +jumpiness/MS +jumpsuit/S +jumpy/PTR +jun +junco/MS +junction/IMESF +juncture/SFM +jungle/SDM +junior/MS +juniority/M +juniper/SM +junk/GZDRMS +junkerdom +junket/SMDG +junketeer/SGDM +junkie/RSMT +junkyard/MS +junta/MS +juridic +juridical/Y +juried +jurisdiction/SM +jurisdictional/Y +jurisprudence/SM +jurisprudent +jurisprudential/Y +jurist/MS +juristic +juror/MS +jury/IMS +jurying +juryman/M +jurymen +jurywoman/M +jurywomen +just/UPY +justed +juster/M +justest +justice/MIS +justiciable +justifiability/M +justifiable/U +justifiably/U +justification/M +justified/UA +justifier/M +justify/GDRSXZN +justing +justness's/U +justness/MS +justs +jut/S +jute/SM +jutted +jutting +juvenile/SM +juxtapose/SDG +juxtaposition/SM +k's/IE +k/FGEIS +kHz/M +kW +kWh +kabob/SM +kaboom +kabuki/SM +kaddish/S +kaffeeklatch +kaffeeklatsch/S +kaftan's +kaiser/MS +kale/MS +kaleidescope +kaleidoscope/SM +kaleidoscopic +kaleidoscopically +kamikaze/SM +kangaroo/SGMD +kaolin/MS +kaolinite/M +kapok/SM +kappa/MS +kaput/M +karakul/MS +karaoke/S +karat/SM +karate/MS +karma/SM +karmic +kart/MS +katakana +katydid/SM +kayak/SGDM +kayo/DMSG +kazoo/SM +kc/M +kcal/M +kebab/SM +keel/GSMDR +keelhaul/SGD +keen/GTSPYDR +keener/M +keening/M +keenness/MS +keep/GZJSR +keeper/M +keeping/M +keepsake/SM +keg/MS +kegged +kegging +kelp/GZMDS +kelvin/MS +ken/MS +kenned +kennel/GSMD +kenning +keno/M +kepi/SM +kept +keratin/MS +kerbside +kerchief/MDSG +kerned +kernel/GSMD +kerning +kerosene/MS +kestrel/SM +ketch/MS +ketchup/SM +ketone/M +ketosis/M +kettle/SM +kettledrum/SM +kettleful +key/SGMD +keyboard/RDMZGS +keyboardist/S +keyclick/SM +keyhole/MS +keynote/SRDZMG +keynoter/M +keypad/MS +keypunch/ZGRSD +keypuncher/M +keyring +keystone/SM +keystroke/SDMG +keyword/SM +kg +khaki/SM +khan/MS +kibble/GMSD +kibbutz/M +kibbutzim +kibitz/GRSDZ +kibitzer/M +kibosh/GMSD +kick/GZDRS +kickback/SM +kickball/MS +kicker/M +kickoff/SM +kickstand/MS +kicky/RT +kid/MS +kidded +kidder/SM +kiddie/SD +kidding/YM +kiddish +kiddo/SM +kiddy's +kiddying +kidless +kidnap/MSJ +kidnaper's +kidnaping's +kidnapped +kidnapper/SM +kidnapping/S +kidney/MS +kidskin/SM +kielbasa/SM +kielbasi +kier/I +kill/BJGZSDR +killdeer/SM +killer/M +killing/Y +killjoy/S +kiln/GDSM +kilo/SM +kilobaud/M +kilobit/S +kilobuck +kilobyte/S +kilocycle/MS +kilogauss/M +kilogram/MS +kilohertz/M +kilohm/M +kilojoule/MS +kiloliter/MS +kilometer/SM +kiloton/SM +kilovolt/SM +kilowatt/SM +kiloword +kilt/MDRGZS +kilter/M +kimono/MS +kin/MS +kind/PSYRT +kinda +kinder/U +kindergarten/MS +kindergärtner/SM +kindhearted/YP +kindheartedness/MS +kindle/AGRSD +kindler/M +kindliness's/U +kindliness/SM +kindling/M +kindly/TUPR +kindness's +kindness/US +kindred/S +kine/SM +kinematic/S +kinematics/M +kinesics/M +kinesthesis +kinesthetic/S +kinesthetically +kinetic/S +kinetically +kinetics/M +kinfolk/S +king/SGYDM +kingbird/M +kingdom/SM +kingfisher/MS +kinglet/M +kingliness/M +kingly/TPR +kingpin/MS +kingship/SM +kink/GSDM +kinkily +kinkiness/SM +kinky/PRT +kinsfolk/S +kinship/SM +kinsman/M +kinsmen/M +kinswoman/M +kinswomen +kiosk/SM +kip/MS +kipped +kipper/DMSG +kipping +kirk/GDMS +kirsch/S +kismet/SM +kiss/DSRBJGZ +kisser/M +kit/MDRGS +kitbag's +kitchen/GDRMS +kitchener/M +kitchenette/SM +kitchenware/SM +kite/SM +kiter/M +kith/MDG +kiths +kitsch/MS +kitschy +kitted +kitten/SGDM +kittenish/YP +kittenishness/M +kitting +kittiwakes +kitty/SM +kiwi/SM +kiwifruit/S +kl +klaxon/M +kleptomania/MS +kleptomaniac/SM +kludge/RSDGMZ +kludger/M +kludgey +klutz/SM +klutziness/S +klutzy/TRP +klystron/MS +km +kn +knack/SGZRDM +knacker/M +knackwurst/MS +knapsack/MS +knave/SM +knavery/MS +knavish/Y +knead/GZRDS +kneader/M +knee/DSM +kneecap/MS +kneecapped +kneecapping +kneeing +kneel/GRS +kneeler/M +kneepad/SM +knell/SMDG +knelt +knew +knick/ZR +knickerbocker/S +knickknack/SM +knife/DSGM +knight/MDYSG +knighthood/MS +knightliness/MS +knightly/P +knish/MS +knit/AU +knits +knitted +knitter/MS +knitting/SM +knitwear/M +knives/M +knob/MS +knobbly +knobby/RT +knock/GZSJRD +knockabout/M +knockdown/S +knocker/M +knockoff/S +knockout/MS +knockwurst's +knoll/MDSG +knot/MS +knothole/SM +knotted +knottiness/M +knotting/M +knotty/TPR +know/GRBSJ +knowable/U +knower/M +knowhow +knowing/RYT +knowingly/U +knowings/U +knowledge/SM +knowledgeable/P +knowledgeableness/M +knowledgeably +known/SU +knuckle/DSMG +knuckleball/R +knuckleduster +knucklehead/MS +knurl/DSG +koala/SM +kohlrabi/M +kohlrabies +kola/SM +kook/GDMS +kookaburra/SM +kookiness/S +kooky/PRT +kopeck/MS +kosher/DGS +kowtow/SGD +kph +kraal/SMDG +kraft/M +kraut/S! +kriegspiel/M +krill/MS +krone/RM +kronor +krypton/SM +króna/M +krónur +ks +kt +kuchen/MS +kudos/M +kudzu/SM +kumquat/SM +kurtosis/M +kvetch/DSG +kw +kyle/M +l's +l/JGVXT +la/MHLG +lab/SM +label's +label/GAZRDS +labeled/U +labeler/M +labellings/A +labia/M +labial/YS +labile +labiodental +labium/M +labor/RDMJSZG +laboratory/MS +labored's/U +labored/PMY +laboredness/M +laborer/M +laboring/MY +laborings/U +laborious/PY +laboriousness/MS +laborsaving +laburnum/SM +labyrinth/M +labyrinthine +labyrinths +lac/SGMDR +lace/MS +laced/U +lacer/M +lacerate/NGVXDS +laceration/M +laces/U +lacewing/MS +lachrymal/S +lachrymose +lacing/M +lack/GRDMS +lackadaisic +lackadaisical/Y +lacker/M +lackey/SMDG +lackluster/S +laconic +laconically +lacquer/ZGDRMS +lacquerer/M +lacrosse/MS +lactate/MNGSDX +lactation/M +lactational/Y +lacteal +lactic +lactose/MS +lacuna/M +lacunae +lacy/RT +lad/XGSJMND +ladder/GDMS +laddie/MS +lade/S +laded/U +laden/U +ladened +ladening +lading/M +ladle/SDGM +lady/SM +ladybird/SM +ladybug/MS +ladyfinger/SM +ladylike/U +ladylove/MS +ladyship/SM +laetrile/S +lag/ZSR +lager/DMG +laggard/MYSP +laggardness/M +lagged +lagging/MS +lagniappe/SM +lagoon/MS +laid/AI +lain +lair/GDMS +laird/MS +laissez +laity/SM +lake/DSRMG +laker/M +lakeside +lallygag/S +lallygagged +lallygagging +lam/MDRSTG +lama/SM +lamasery/MS +lamb/SRDMG +lambada/S +lambaste/SDG +lambda/SM +lambency/MS +lambent/Y +lambkin/MS +lambskin/MS +lambswool +lame/SPY +lamebrain/SM +lamed/M +lameness/MS +lament/DGSB +lamentable/P +lamentableness/M +lamentably +lamentation/SM +lamented/U +lamina/M +laminae +laminar +laminate/XNGSD +lamination/M +lammed +lammer +lamming +lamp/SGMRD +lampblack/SM +lamplight/ZRMS +lamplighter/M +lampoon/RDMGS +lampooner/M +lamppost/SM +lamprey/MS +lampshade/MS +lanai/SM +lance/SRDGMZ +lancer/M +lancet/MS +land/SMRDJGZ +landau/MS +lander/I +landfall/SM +landfill/DSG +landforms +landhold/JGZR +landholder/M +landing/M +landlady/MS +landless +landlines +landlocked +landlord/MS +landlubber/SM +landmark/GSMD +landmass/MS +landowner/MS +landownership/M +landowning/SM +lands/I +landscape/GMZSRD +landscaper/M +landslid/G +landslide/MS +landslip +landsman/M +landsmen +landward/S +lane/SM +language/MS +languid/PY +languidness/MS +languish/SRDG +languisher/M +languishing/Y +languor/SM +languorous/Y +lank/PTYR +lankiness/SM +lankness/MS +lanky/PRT +lanolin/MS +lantern/GSDM +lanthanide/M +lanthanum/MS +lanyard/MS +lap/SM +lapboard/MS +lapdog/S +lapel/MS +lapidary/MS +lapin/MS +lapped +lappet/MS +lapping +laps/SRDG +lapse/KSDMG +lapsed/A +lapser/MA +lapses/A +lapsing/A +laptop/SM +lapwing/MS +larboard/MS +larcenist/S +larcenous +larceny/MS +larch/MS +lard/MRDSGZ +larder/M +lardy/RT +large/SRTYP +largehearted +largemouth +largeness/SM +largess/SM +largish +largo/S +lariat/MDGS +lark/GRDMS +larker/M +larkspur/MS +larva/M +larvae +larval +laryngeal/YS +larynges +laryngitides +laryngitis/M +larynx/M +las/SRZG +lasagna/S +lasagne's +lascivious/YP +lasciviousness/MS +lase +laser/M +lash/JGMSRD +lashed/U +lasher/M +lashing/M +lass/SM +lassie/SM +lassitude/MS +lasso/GRDMS +lassoer/M +last/JGSYRD +laster/M +lasting/PY +lastingness/M +lat/SDRT +latch's +latch/UGSD +latching/M +latchkey/SM +late/KA +latecomer/SM +lated/A +lately +latency/MS +lateness/MS +latent/YS +later/A +lateral/GDYS +lateralization +latest/S +latex/MS +lath/MSRDGZ +lathe/M +lather/RDMG +latherer/M +lathery +lathing/M +laths +latices/M +latish +latitude/SM +latitudinal/Y +latitudinarian/S +latitudinary +latrine/MS +latte/SR +latter/YM +lattice/SDMG +latticework/MS +latticing/M +laud/RDSBG +laudably +laudanum/MS +laudatory +lauder/M +lauds/M +laugh/BRDZGJ +laughable/P +laughableness/M +laughably +laugher/M +laughing/MY +laughingstock/SM +laughs +laughter/MS +launch/AGSD +launcher/MS +launching/S +launchpad/S +launder/SDRZJG +laundered/U +launderer/M +launderette/MS +laundress/MS +laundrette/S +laundromat/S +laundry/MS +laundryman/M +laundrymen +laundrywoman/M +laundrywomen +laura/M +laureate/DSNG +laureateship/SM +laurel/SGMD +lava/SM +lavage/MS +lavaliere/MS +lavatory/MS +lave/GDS +lavender/MDSG +lavish/SRDYPTG +lavishness/MS +law/SMDG +lawbreaker/SM +lawbreaking/MS +lawful/PUY +lawfulness/SMU +lawgiver/MS +lawgiving/M +lawless/PY +lawlessness/MS +lawmaker/MS +lawmaking/SM +lawman/M +lawmen +lawn/SM +lawnmower/S +lawrencium/SM +lawsuit/MS +lawyer/DYMGS +lax/PTSRY +laxative/PSYM +laxativeness/M +laxer/A +laxes/A +laxity/SM +laxness/SM +lay/CZGSR +layabout/MS +layaway/S +layer's/IC +layer/GJDM +layered/C +layering/M +layette/SM +layman/M +laymen +layoff/MS +layout/SM +layover/SM +laypeople +layperson/S +lays/AI +layup/MS +laywoman/M +laywomen +laze/DSG +lazily +laziness/MS +lazuli/M +lazy/PTSRDG +lazybones/M +lb +lbs +lea/MS +leach/SDG +leachate +lead/SGZXJRDN +leaded/U +leaden/PGDY +leadenness/M +leader/M +leaderless +leadership/MS +leadsman/M +leadsmen +leaf/GSDM +leafage/MS +leafhopper/M +leafiness/M +leafless +leaflet/SDMG +leafstalk/SM +leafy/PTR +league/RSDMZG +leaguer/M +leak/GSRDM +leakage/SM +leaker/M +leakiness/MS +leaky/PRT +lean/YRDGTJSP +leaner/M +leaning/M +leanness/MS +leap/RDGZS +leaper/M +leapfrog/SM +leapfrogged +leapfrogging +learn/SZGJRD +learned/UA +learnedly +learnedness/M +learner/M +learning/M +learns/UA +leas/SRDGZ +lease's +lease/ARSDG +leaseback/MS +leasehold/SRMZ +leaseholder/M +leaser/MA +leash's +leash/UGSD +leasing/M +least/S +leastwise +leather/MDSG +leatherette/S +leathern +leatherneck/SM +leathery +leave/SRDJGZ +leaven/DMJGS +leavened/U +leavening/M +leaver/M +leaves/M +leaving/M +lebensraum +lecher/DMGS +lecherous/YP +lecherousness/MS +lechery/MS +lecithin/SM +lectern/SM +lecture/RSDZMG +lecturer/M +lectureship/SM +led +ledge/SRMZ +ledger/DMG +lee/MZRS +leech/MSDG +leek/SM +leer/DG +leeriness/MS +leering/Y +leery/PTR +leeward/S +leeway/MS +left/TRS +leftism/SM +leftist/SM +leftmost +leftover/MS +leftward/S +lefty/SM +leg/MS +legacy/MS +legal/SY +legalese/MS +legalism/SM +legalistic +legality/MS +legalization/MS +legalize/DSG +legalized/U +legate's/C +legate/AXCNGSD +legatee/MS +legation/AMC +legato/SM +legend/SM +legendarily +legendary/S +legerdemain/SM +legged +legginess/MS +legging/MS +leggy/PRT +leghorn/SM +legibility/MS +legible +legibly +legion/SM +legionary/S +legionnaire/SM +legislate/SDXVNG +legislation/M +legislative/SY +legislator/SM +legislature/MS +legit/S +legitimacy/MS +legitimate/SDNGY +legitimation/M +legitimatize/SDG +legitimization/MS +legitimize/RSDG +legless +legman/M +legmen +legroom/MS +legstraps +legume/SM +leguminous +legwork/SM +lei/MS +leisure/SDYM +leisureliness/MS +leisurely/P +leisurewear +leitmotif/SM +leitmotiv/MS +lemma/MS +lemme/GJ +lemming/M +lemon/GSDM +lemonade/SM +lemony +lemur/MS +lend/SRGZ +lender/M +length/MNYX +lengthen/GRD +lengthener/M +lengthily +lengthiness/MS +lengths +lengthwise +lengthy/TRP +lenience/S +leniency/MS +lenient/SY +lenitive/S +lens/SRDMJGZ +lent/A +lenticular +lentil/SM +lento/S +leonine +leopard/MS +leopardess/SM +leopardskin +leotard/MS +leper/SM +leprechaun/SM +leprosy/MS +leprous +lepta +lepton/SM +lesbian/MS +lesbianism/MS +lesion/DMSG +less/U +lessee/MS +lessen/GDS +lesser +lesses +lessing +lesson/DMSG +lessor/MS +lest/R +let/ISM +letdown/SM +lethal/YS +lethality/M +lethargic +lethargically +lethargy/MS +letter/JSZGRDM +letterbox/S +lettered/U +letterer/M +letterhead/SM +lettering/M +letterman/M +lettermen +letterpress/MS +letting/S +lettuce/SM +letup/MS +leukemia/SM +leukemic/S +leukocyte/MS +levee/SDM +leveeing +level/STZGRDYP +leveled/U +leveler/M +levelheaded/P +levelheadedness/S +leveling/U +levelness/SM +lever/SDMG +leverage/MGDS +leviathan/MS +levier/M +levitate/XNGDS +levitation/M +levity/MS +levy/SRDZG +lewd/PYRT +lewdness/MS +lewis/M +lex +lexeme/MS +lexical/Y +lexicographer/MS +lexicographic +lexicographical/Y +lexicography/SM +lexicon/SM +lg +liability/SAM +liable/AP +liaise/GSD +liaison/SM +liar/MS +lib/MS +libation/SM +libbed +libbing +libel/GMRDSZ +libeler/M +libelous/Y +liberal/YSP +liberalism/MS +liberality/MS +liberalization/SM +liberalize/GZSRD +liberalized/U +liberalizer/M +liberalness/MS +liberate/NGDSCX +liberation/MC +liberationists +liberator/SCM +libertarian/MS +libertarianism/M +libertine/MS +liberty/MS +libidinal +libidinous/PY +libidinousness/M +libido/MS +librarian/MS +library/MS +libretoes +libretos +librettist/MS +libretto/MS +lice/M +license/MGBRSD +licensed/AU +licensee/SM +licenser/M +licenses/A +licensing/A +licensor/M +licentiate/MS +licentious/PY +licentiousness/MS +lichee's +lichen/DMGS +licit/Y +lick/GRDSJ +licked/U +licker/M +lickerish +licking/M +licorice/SM +lid/MS +lidded +lidding +lidless +lido/MS +lie/DRS +lied/MR +lief/TSR +liefs/A +liege/SR +lien/SM +lier/IMA +lies/A +lieu/SM +lieut +lieutenancy/MS +lieutenant/SM +life/MZR +lifeblood/SM +lifeboat/SM +lifebuoy/S +lifeforms +lifeguard/MDSG +lifeless/PY +lifelessness/SM +lifelike/P +lifelikeness/M +lifeline/SM +lifelong +lifer/M +lifesaver/SM +lifesaving/S +lifespan/S +lifestyle/S +lifetaking +lifetime/MS +lifework/MS +lift/GZMRDS +lifter/M +liftoff/MS +ligament/MS +ligand/MS +ligate/XSDNG +ligation/M +ligature/DSGM +light's +light/ADSCG +lighted/U +lighten/ZGDRS +lightener/M +lightening/M +lighter/CM +lightered +lightering +lighters +lightest +lightface/SDM +lightheaded +lighthearted/PY +lightheartedness/MS +lighthouse/MS +lighting/MS +lightly +lightness/MS +lightning/SMD +lightproof +lightship/SM +lightweight/S +ligneous +lignite/MS +lignum +likability/MS +likable/P +likableness/MS +like/USPBY +likeability's +liked/E +likelihood/MSU +likely/UPRT +liken/GSD +likeness/MSU +liker's +liker/E +likes/E +likest +likewise +liking/SM +lilac/MS +lilliputian/S +lilt/MDSG +lilting/YP +lily/MSD +limb/SGZRDM +limber/RDYTGP +limbered/U +limberness/SM +limbers/U +limbic +limbless +limbo/GDMS +lime/DSMG +limeade/SM +limekiln/M +limelight/DMGS +limerick/SM +limestone/SM +limit's +limit/CSZGRD +limitability +limitably +limitation/MCS +limited/PSY +limitedly/U +limitedness/M +limiter/M +limiting/S +limitless/PY +limitlessness/SM +limn/GSD +limo/S +limousine/SM +limp/SGTPYRD +limper/M +limpet/SM +limpid/YP +limpidity/MS +limpidness/SM +limpness/MS +limy/TR +linage/MS +linchpin/MS +linden/MS +line's +line/AGDS +lineage/SM +lineal/Y +lineament/MS +linear/Y +linearity/MS +linearize/SDGNB +linebacker/SM +lined/U +linefeed +lineman/M +linemen +linen/SM +liner/SM +linesman/M +linesmen +lineup/S +ling/ZR +linger/ZGJRD +lingerer/M +lingerie/SM +lingering/Y +lingo/M +lingoes +lingua/M +lingual/SY +linguine +linguini's +linguist/SM +linguistic/S +linguistically +linguistics/M +liniment/MS +lining/SM +link's +link/USGD +linkable +linkage/SM +linked/A +linker/S +linking/S +linkup/S +linnet/SM +lino/M +linoleum/SM +linseed/SM +lint/SMR +lintel/SM +linter/M +linty/RST +lion/MS +lioness/SM +lionhearted +lionization/SM +lionize/ZRSDG +lionizer/M +lip/MS +lipase/M +lipid/MS +liposuction/S +lipped +lipper +lipping +lippy/TR +lipread/GSRJ +lipstick/MDSG +liq +liquefaction/SM +liquefier/M +liquefy/DRSGZ +liqueur/DMSG +liquid/SPMY +liquidate/GNXSD +liquidation/M +liquidator/SM +liquidity/SM +liquidize/ZGSRD +liquidizer/M +liquidness/M +liquor/SDMG +liquorice/SM +liquorish +lira/M +lire +lisle/SM +lisp/MRDGZS +lisper/M +lissome/P +lissomeness/M +lissomness/M +list/JMRDNGZXS +listed/U +listen/ZGRD +listener/M +lister/M +listing/M +listless/PY +listlessness/SM +lit/RZS +litany/MS +litchi/SM +lite/S +liter/M +literacy/MS +literal/PYS +literalism/M +literalistic +literalness/MS +literariness/SM +literary/P +literate/YNSP +literati +literation/M +literature/SM +lithe/PRTY +litheness/SM +lithesome +lithium/SM +lithograph/DRMGZ +lithographer/M +lithographic +lithographically +lithographs +lithography/MS +lithology/M +lithosphere/MS +lithospheric +litigant/MS +litigate/NGXDS +litigation/M +litigator/SM +litigious/PY +litigiousness/MS +litmus/SM +litotes/M +litter/SZGRDM +litterbug/SM +little/RSPT +littleneck/M +littleness/SM +littoral/S +littérateur/S +liturgic/S +liturgical/Y +liturgics/M +liturgist/MS +liturgy/SM +livability/MS +livable/U +livableness/M +livably +live/YHZTGJDSRPB +lived/A +livelihood/SM +liveliness/SM +livelong/S +lively/RTP +liven/SDG +liveness/M +liver's +liver/CSGD +liveried +liverish +liverwort/SM +liverwurst/SM +livery/CMS +liveryman/MC +liverymen/C +lives's +lives/A +livestock/SM +livid/YP +lividness/M +living/YP +livingness/M +lizard/MS +ll/C +llama/SM +llano/SM +lo +load's/A +load/SURDZG +loadable +loaded/A +loader/MU +loading/MS +loads/A +loadstar's +loadstone's +loaf/SRDMGZ +loafer/M +loam/SMDG +loamy/RT +loan/SGZRDMB +loaner/M +loaning/M +loansharking/S +loanword/S +loath/JPSRDYZG +loathe +loather/M +loathing/M +loathness/M +loathsome/PY +loathsomeness/MS +loaves/M +lob/MDSG +lobar +lobbed +lobber/MS +lobbing +lobby/GSDM +lobbyist/MS +lobe/SM +lobotomist +lobotomize/GDS +lobotomy/MS +lobster/MDGS +lobular/Y +lobularity +lobule/SM +local/SGDY +locale/MS +localisms +locality/MS +localization/MS +localize/ZGDRS +localized/U +localizer/M +localizes/U +locatable +locate/AXESDGN +locater/M +location/EMA +locational/Y +locative/S +locator's +loch/M +lochs +loci/M +lock's +lock/UGSD +lockable +locked/A +locker/SM +locket/SM +locking/S +lockjaw/SM +locknut/M +lockout/MS +locksmith/MG +locksmithing/M +locksmiths +lockstep/S +lockup/MS +loco/SDMG +locomotion/SM +locomotive/YMS +locomotor +locomotory +locoweed/MS +locus/M +locust/SM +locution/MS +lode/SM +lodestar/MS +lodestone/MS +lodge/GMZSRDJ +lodged/E +lodgepole +lodger/M +lodges/E +lodging/M +lodgment/M +loft/SGMRD +lofter/M +loftily +loftiness/SM +lofty/PTR +log's/K +log/SM +loganberry/SM +logarithm/MS +logarithmic +logarithmically +logbook/MS +loge/SMNX +logged/U +logger/SM +loggerhead/SM +loggia/SM +logging/MS +logic/SM +logical/SPY +logicality/MS +logicalness/M +logician/SM +login/S +logion/M +logistic/MS +logistical/Y +logjam/SM +logo/SM +logotype/MS +logout +logrolling/SM +logy/RT +loin/SM +loincloth/M +loincloths +loiter/RDJSZG +loiterer/M +loll/RDGS +loller/M +lollipop/MS +lolly/SM +lone/PYZR +loneliness/SM +lonely/TRP +loneness/M +loner/M +lonesome/PSY +lonesomeness/MS +long/JGTYRDPS +longboat/MS +longbow/SM +longed/K +longeing +longer/K +longevity/MS +longhair/SM +longhand/SM +longhorn/SM +longing/MY +longish +longitude/MS +longitudinal/Y +longness/M +longs/K +longshoreman/M +longshoremen +longsighted +longstanding +longsword +longterm +longtime +longueur/SM +longways +longword/SM +loofah/M +loofahs +look/GZRDS +lookahead +lookalike/S +looker/M +lookout/MS +lookup/SM +loom/MDGS +looming/M +loon/MS +loony/SRT +loop/MRDGS +looper/M +loophole/MGSD +loopy/TR +loose/SRDPGTY +loosed/U +looseleaf +loosen/UDGS +loosener/M +looseness/MS +looses/U +loosing/M +loot/MRDGZS +looter/M +lop/SDRG +lope/S +loper/M +lopped +lopper/MS +lopping +lopsided/YP +lopsidedness/SM +loquacious/YP +loquaciousness/MS +loquacity/SM +lord/MYDGS +lording/M +lordliness/SM +lordly/PTR +lordship/SM +lore/MS +lorgnette/SM +loris/SM +lorn +lorry/SM +lorryload/S +lose/ZGJBSR +loser/M +loss/SM +lossage +lossless +lossy/RT +lost/P +lot/MS +lotion/MS +lotted +lotter +lottery/MS +lotting +lotto/MS +lotus/SM +loud/YRNPT +louden/DG +loudhailer/S +loudly/RT +loudmouth/DM +loudmouths +loudness/MS +loudspeaker/SM +loudspeaking +lounge/SRDZG +lounger/M +lour/GSD +louse's +louse/CSDG +lousewort/M +lousily +lousiness/MS +lousy/PRT +lout/SGMD +loutish/YP +loutishness/M +louver/DMS +lovable/U +lovableness/MS +lovably +love/DSRMYZGJB +lovebird/SM +lovechild +loved/U +loveless/YP +lovelessness/M +lovelies +loveliness/UM +lovelinesses +lovelorn/P +lovelornness/M +lovely/URPT +lovemaking/SM +lover/YMG +lovesick +lovestruck +loving/U +lovingly +lovingness/M +low/PDRYSZTG +lowborn +lowboy/SM +lowbrow/MS +lowdown/S +lower/DG +lowercase/GSD +lowermost +lowish +lowland/RMZS +lowlife/SM +lowlight/MS +lowliness/MS +lowly/PTR +lowness/MS +lox/MDSG +loyal/EY +loyaler +loyalest +loyalism/SM +loyalist/SM +loyalty/EMS +lozenge/SDM +ls +ltd +luau/MS +lubber/YMS +lube/DSMG +lubricant/SM +lubricate/VNGSDX +lubrication/M +lubricator/MS +lubricious/Y +lubricity/SM +lucent/Y +lucid/YP +lucidity/MS +lucidness/MS +luck/GSDM +luckier/U +luckily/U +luckiness/UMS +luckless +lucky/RSPT +lucrative/YP +lucrativeness/SM +lucre/MS +lucubrate/GNSDX +lucubration/M +ludicrous/PY +ludicrousness/SM +ludo/M +luff/GSDM +lug/RS +luge/MC +luggage/SM +lugged +lugger/SM +lugging +lugsail/SM +lugubrious/YP +lugubriousness/MS +lukewarm/PY +lukewarmness/SM +lull/SDG +lullaby/GMSD +lulu/M +lumbago/SM +lumbar/S +lumber/RDMGZSJ +lumberer/M +lumbering/M +lumberjack/MS +lumberman/M +lumbermen +lumberyard/MS +lumen/M +luminance/M +luminary/MS +luminescence/SM +luminescent +luminosity/MS +luminous/YP +luminousness/M +lummox/MS +lump/SGMRDN +lumper/M +lumpiness/MS +lumpish/YP +lumpishness/M +lumpy/TPR +lunacy/MS +lunar/S +lunary +lunate/YND +lunatic/S +lunation/M +lunch/GMRSD +luncheon/SMDG +luncheonette/SM +luncher/M +lunchpack +lunchroom/MS +lunchtime/MS +lune/M +lung/SGRDM +lunge/MS +lunger/M +lungfish/SM +lungful +lunkhead/SM +lupine/SM +lupus/SM +lurch/RSDG +lurcher/M +lure/DSRG +lurer/M +lurex +lurid/YP +luridness/SM +lurk/GZSRD +lurker/M +luscious/PY +lusciousness/MS +lush/YSRDGTP +lushness/MS +lust/MRDGZS +luster/GDM +lustering/M +lusterless +lustful/PY +lustfulness/M +lustily +lustiness/MS +lustrous/PY +lustrousness/M +lusty/PRT +lutanist/MS +lute/DSMG +lutenist/MS +lutetium/MS +luting/M +luxe/MS +luxuriance/MS +luxuriant/Y +luxuriate/GNSDX +luxuriation/M +luxurious/PY +luxuriousness/SM +luxury/MS +lyceum/MS +lychee's +lycopodium/M +lye/JSMG +lying/Y +lymph/M +lymphatic/S +lymphocyte/SM +lymphoid +lymphoma/MS +lymphs +lynch/ZGRSDJ +lyncher/M +lynching/M +lynx/MS +lyre/SM +lyrebird/MS +lyric/S +lyrical/YP +lyricalness/M +lyricism/SM +lyricist/SM +lysine/M +m's/K +m/ASK +ma'am +ma/MH +mac/SGMDR +macabre/Y +macadam/SM +macadamize/SDG +macaque/SM +macaroni/SM +macaroon/MS +macaw/SM +mace/MS +macer/M +macerate/DSXNG +maceration/M +machete/SM +machinate/SDXNG +machination/M +machine/MGSDB +machinelike +machinery/SM +machinist/MS +machismo/SM +macho/S +macintosh's +mack/M +mackerel/SM +mackinaw/SM +mackintosh/SM +macramé/S +macro/SM +macrobiotic/S +macrobiotics/M +macrocosm/MS +macrodynamic +macroeconomic/S +macroeconomics/M +macromolecular +macromolecule/SM +macron/MS +macrophage/SM +macroscopic +macroscopically +macrosimulation +macrosocioeconomic +mad/PSY +madam/SM +madame/M +madcap/S +madded +madden/GSD +maddening/Y +madder/MS +maddest +madding +made/AU +mademoiselle/MS +madhouse/SM +madman/M +madmen +madness/SM +madras/SM +madrigal/MSG +madwoman/M +madwomen +maelstrom/SM +maestro/MS +mafia/S +mafiosi +mafioso/M +mag/S +magazine/DSMG +magenta/MS +magged +magging +maggot/MS +maggoty/RT +magi +magic/SM +magical/Y +magician/MS +magicked +magicking +magisterial/Y +magistracy/MS +magistrate/MS +magma/SM +magnanimity/SM +magnanimosity +magnanimous/PY +magnate/SM +magnesia/MS +magnesite/M +magnesium/SM +magnet/SM +magnetic/S +magnetically +magnetics/M +magnetism/SM +magnetite/SM +magnetizable +magnetization/ASCM +magnetize/CGDS +magnetized/U +magneto/MS +magnetodynamics +magnetohydrodynamical +magnetohydrodynamics/M +magnetometer/MS +magnetosphere/M +magnetron/M +magnification/M +magnificence/SM +magnificent/Y +magnified/U +magnify/DRSGNXZ +magniloquence/MS +magniloquent +magnitude/SM +magnolia/SM +magnum/SM +magpie/SM +maharajah/M +maharajahs +maharanee's +maharani/MS +maharishi/SM +mahatma/SM +mahjong's +mahogany/MS +mahout/SM +maid/SMNX +maiden/YM +maidenhair/MS +maidenhead/SM +maidenhood/SM +maidenly/P +maidservant/MS +maier +mail/BSJGZMRD +mailbag/MS +mailbox/MS +mailer/M +maillot/SM +mailman/M +mailmen +maim/SGZRD +maimed/P +maimedness/M +maimer/M +main/SA +mainbrace/M +mainframe/MS +mainland/SRMZ +mainlander/M +mainline/RSDZG +mainliner/M +mainly +mainmast/SM +mains/M +mainsail/SM +mainspring/SM +mainstay/MS +mainstream/DRMSG +maintain/BRDZGS +maintainability +maintainable/U +maintained/U +maintainer/M +maintenance/SM +maintop/SM +maiolica's +maisonette/MS +maize/MS +majestic +majestically +majesty/MS +majolica/SM +major/DMGS +majordomo/S +majorette/SM +majority/SM +makable +make/UGSA +makefile/S +makeover/S +maker/SM +makeshift/S +makeup/MS +making/SM +malachite/SM +maladapt/DV +maladjust/DLV +maladjustment/MS +maladministration +maladroit/YP +maladroitness/MS +malady/MS +malaise/SM +malamute/SM +malaprop +malapropism/SM +malaria/MS +malarial +malarious +malarkey/SM +malathion/S +malcontent/SMD +malcontented/PY +malcontentedness/M +male/PSM +maledict +malediction/MS +malefaction/MS +malefactor/MS +malefic +maleficence/MS +maleficent +maleness/MS +malevolence/S +malevolencies +malevolent/Y +malfeasance/SM +malfeasant +malformation/MS +malformed +malfunction/SDG +malice/MGSD +malicious/YU +maliciousness/MS +malign/GSRDYZ +malignancy/SM +malignant/YS +malignity/MS +malinger/GZRDS +malingerer/M +mall/SGMD +mallard/SM +malleability/SM +malleable/P +malleableness/M +mallet/MS +mallow/MS +malnourished +malnutrition/SM +malocclusion/MS +malodorous +malposed +malpractice/SM +malt/SGMD +malted/S +malting/M +maltose/SM +maltreat/GDSL +maltreatment/S +malty/RT +mama/SM +mamba/SM +mambo/GSDM +mamma's +mammal/SM +mammalian/SM +mammary +mammogram/S +mammography/S +mammon/SM +mammoth/M +mammoths +mammy/SM +man's +man/USY +manacle/SDMG +manage/ZLGRSD +manageability/S +manageable/U +manageableness +managed/U +management/SM +manager/M +manageress/M +managerial/Y +managership/M +mananas +manatee/SM +manciple/M +mandala/SM +mandamus/GMSD +mandarin/MS +mandate/SDMG +mandatory/S +mandible/MS +mandibular +mandolin/MS +mandrake/MS +mandrel/SM +mandrill/SM +mane/MDS +maneuver/MRDSGB +maneuverability/MS +maneuverer/M +manful/Y +manganese/MS +mange/GMSRDZ +manger/M +manginess/S +mangle/RSDG +mangler/M +mango/M +mangoes +mangrove/MS +mangy/PRT +manhandle/GSD +manhole/MS +manhood/MS +manhunt/SM +mania/SM +maniac/SM +maniacal/Y +manic/S +manically +manicure/MGSD +manicurist/SM +manifest/YDPGS +manifestation/SM +manifesto/GSDM +manifold/GPYRDMS +manifolder/M +manifoldness/M +manikin/MS +manila/S +manilla's +manioc/SM +manipulability +manipulable +manipulate/SDXBVGN +manipulative/PM +manipulator/MS +manipulatory +mankind/M +manlike +manliness's/U +manliness/SM +manly/URPT +manna/MS +manned/U +mannequin/MS +manner/SDYM +mannered/U +mannerism/SM +mannerist/M +mannerliness/MU +mannerly/UP +mannikin's +manning/U +mannish/YP +mannishness/SM +manometer/SM +manor/MS +manorial +manpower/SM +manqué/M +mans/S +mansard/SM +manse/XNM +manservant/M +mansion/M +manslaughter/SM +manta/MS +mantel/SM +mantelpiece/MS +mantes +mantilla/MS +mantis/SM +mantissa/SM +mantle's +mantle/ESDG +mantling/M +mantra/MS +mantrap/SM +manual/SMY +manufacture/JZGDSR +manufacturer/M +manumission/MS +manumit/S +manumitted +manumitting +manure/RSDMZG +manuscript/MS +many +manège/GSD +map/SM +maple/MS +mapmaker/S +mappable +mapped/UA +mapper/S +mapping/MS +maps/AU +mar/S +marabou/MS +marabout's +maraca/MS +maraschino/SM +marathon/MRSZ +marathoner/M +maraud/ZGRDS +marauder/M +marble/JRSDMG +marbleize/GSD +marbler/M +marbling/M +march/RSDZG +marcher/M +marchioness/SM +mare/MS +margarine/MS +margarita/SM +margin/GSDM +marginal/YS +marginalia +marginality +marginalization +marginalize/SDG +maria/M +mariachi/SM +marigold/MS +marijuana/SM +marimba/SM +marina/MS +marinade/MGDS +marinara/SM +marinate/NGXDS +marination/M +marine/ZRS +mariner/M +marionette/MS +marital/Y +maritime/R +marjoram/SM +mark/GZRDMBSJ +markdown/SM +marked/AU +markedly +marker/M +market/GSMRDJBZ +marketability/SM +marketable/U +marketeer/S +marketer/M +marketing/M +marketplace/MS +marking/M +markka/M +markkaa +marks/A +marksman/M +marksmanship/S +marksmen +markup/SM +marl/MDSG +marlin/SM +marlinespike/SM +marmalade/MS +marmoreal +marmoset/MS +marmot/SM +maroon/GRDS +marque/SM +marquee/MS +marquess/MS +marquetry/SM +marquis/SM +marquise/M +marquisette/MS +marred/U +marriage/ASM +marriageability/SM +marriageable +married/US +marring +marrow/GDMS +marrowbone/MS +marry/SDGA +marsh/MS +marshal/GMDRSZ +marshaller +marshallings +marshiness/M +marshland/MS +marshmallow/SM +marshy/PRT +marsupial/MS +mart/MDNGXS +marten/M +martial/Y +martin/SM +martinet/SM +martingale/MS +martini/MS +martyr/GDMS +martyrdom/SM +marvel/DGS +marvelous/PY +marzipan/SM +mas/SRZ +masc +mascara/SGMD +mascot/SM +masculine/PYS +masculineness/M +masculinity/SM +maser/M +mash/JGZMSRD +mask/GZSRDMJ +masked/U +masker/M +masks/U +masochism/MS +masochist/MS +masochistic +masochistically +mason/SDMG +masonic +masonry/MS +masque/RSMZ +masquer/M +masquerade/RSDGMZ +masquerader/M +mass/VGSD +massacre/DRSMG +massage/SRDMG +massager/M +masseur/MS +masseuse/SM +massif/SM +massing/R +massive/YP +massiveness/SM +massless +mast/GZSMRD +mastectomy/MS +master/JGDYM +masterclass +mastered/A +masterful/YP +masterfulness/M +masterliness/M +masterly/P +mastermind/GDS +masterpiece/MS +mastership/M +masterstroke/MS +masterwork/S +mastery/MS +masthead/SDMG +mastic/SM +masticate/SDXGN +mastication/M +mastiff/MS +mastodon/MS +mastoid/S +masturbate/SDNGX +masturbation/M +masturbatory +mat/SJGMDR +matador/SM +match's/A +match/BMRSDZGJ +matchable/U +matchbook/SM +matchbox/SM +matched/UA +matcher/M +matches/A +matchless/Y +matchlock/MS +matchmake/GZJR +matchmaker/M +matchmaking/M +matchplay +matchstick/MS +matchwood/SM +mate/IMS +mated/U +mater/M +material/SPYM +materialism/SM +materialist/SM +materialistic +materialistically +materiality/M +materialization/SM +materialize/CDS +materialized/A +materializer/SM +materializes/A +materializing +materialness/M +maternal/Y +maternity/MS +mates/U +math/M +mathematic/S +mathematical/Y +mathematician/SM +mathematics/M +maths +mating/M +matins/M +matinée/S +matriarch/M +matriarchal +matriarchs +matriarchy/MS +matrices +matricidal +matricide/MS +matriculate/XSDGN +matriculation/M +matrimonial/Y +matrimony/SM +matrix/M +matron/YMS +matt's +matte/JGMZSRD +matter/GDM +matting/M +mattins's +mattock/MS +mattress/MS +maturate/DSNGVX +maturation/M +maturational +mature/RSDTPYG +matureness/M +maturer/M +maturity/MS +matzo/SHM +matzot +matériel/MS +maudlin/Y +maul/RDGZS +mauler/M +maunder/GDS +mausoleum/SM +mauve/SM +maven/S +maverick/SMDG +mavin's +maw/SGMD +mawkish/PY +mawkishness/SM +max/GDS +maxi/S +maxilla/M +maxillae +maxillary/S +maxim/SM +maxima's +maximal/SY +maximality +maximization/SM +maximize/RSDZG +maximizer/M +maximum/MYS +maxwell/M +may/EGS +maybe/S +mayday/S +mayer +mayest +mayflower/SM +mayfly/MS +mayhap +mayhem/MS +mayn't +mayo/S +mayonnaise/MS +mayor/MS +mayoral +mayoralty/MS +mayoress/MS +mayorship/M +maypole/MS +mayst +maze/MGDSR +mazed/YP +mazedness/SM +mazurka/SM +mañana/M +mdse +me/G +mead/SM +meadow/MS +meadowland +meadowlark/SM +meadowsweet/M +meager/PY +meagerness/SM +meagres +meal/MDGS +mealiness/MS +mealtime/MS +mealy/PRST +mealybug/S +mealymouthed +mean/YRGJTPS +meander/JDSG +meaneing +meanie/MS +meaning/M +meaningful/YP +meaningfulness/SM +meaningless/PY +meaninglessness/SM +meanness/S +means/M +meant/U +meantime/SM +meanwhile/S +meany's +meas/Y +measle/SD +measles/M +measly/TR +measurable/U +measurably +measure/BLMGRSD +measured/Y +measureless +measurement/SM +measurer/M +measures/A +measuring/A +meat/MS +meataxe +meatball/MS +meatiness/MS +meatless +meatloaf +meatloaves +meatpacking/S +meaty/RPT +mecca/S +mechanic/MS +mechanical/YS +mechanism/SM +mechanist/M +mechanistic +mechanistically +mechanization/SM +mechanize/RSDZGB +mechanized/U +mechanizer/M +mechanizes/U +mechanochemically +med +medal/SGMD +medalist/MS +medallion/MS +meddle/GRSDZ +meddlesome +media/SM +mediaeval's +medial/AY +medials +median/YMS +mediate/PSDYVNGX +mediateness/M +mediation/ASM +mediator/SM +medic/SM +medical/YS +medicament/MS +medicate/DSXNGV +medication/M +medicinal/SY +medicine/DSMG +medico/SM +medieval/YMS +medievalist/MS +mediocre +mediocrity/MS +meditate/NGVXDS +meditation/M +meditative/PY +meditativeness/M +medium/SM +mediumistic +medley/SM +medulla/SM +meed/MS +meek/TPYR +meekness/MS +meerschaum/MS +meet/JGSYR +meeter/M +meeting/M +meetinghouse/S +mega +megabit/MS +megabuck/S +megabyte/S +megacycle/MS +megadeath/M +megadeaths +megahertz/M +megalith/M +megalithic +megaliths +megalomania/SM +megalomaniac/SM +megalopolis/SM +megaphone/SDGM +megaton/MS +megavolt/M +megawatt/SM +megaword/S +megohm/MS +meioses +meiosis/M +meiotic +melamine/SM +melancholia/SM +melancholic/S +melancholy/MS +melange/S +melanin/MS +melanoma/SM +meld/SGD +meliorate/XSDVNG +melioration/M +mellifluous/YP +mellifluousness/SM +mellow/TGRDYPS +mellowness/MS +melodic/S +melodically +melodious/YP +melodiousness/S +melodrama/SM +melodramatic/S +melodramatically +melody/MS +melon/MS +melt/SAGD +meltdown/S +melter/M +melting/Y +member/DMS +membered/AE +members/EA +membership/SM +membrane/MSD +membranous +memento/SM +memo/SM +memoir/MS +memorabilia +memorability/SM +memorable/P +memorableness/M +memorably +memorandum/SM +memorial/SY +memorialize/DSG +memorialized/U +memoriam +memorization/MS +memorize/RSDZG +memorized/U +memorizer/M +memorizes/A +memory/MS +memoryless +men/MS +menace/GSD +menacing/Y +menage/S +menagerie/SM +menarche/MS +mend/RDSJGZ +mendacious/PY +mendaciousness/M +mendacity/MS +mendelevium/SM +mender/M +mendicancy/MS +mendicant/S +mending/M +menfolk/S +menhaden/M +menial/YS +meningeal +meninges +meningitides +meningitis/M +meninx +menisci +meniscus/M +menopausal +menopause/SM +menorah/M +menorahs +mens/SDG +mensch/S +menservants/M +menstrual +menstruate/NGDSX +menstruation/M +mensurable/P +mensuration/MS +menswear/M +mental/Y +mentalist/MS +mentality/MS +menthol/SM +mentholated +mention/ZGBRDS +mentionable/U +mentioned/U +mentioner/M +mentor/DMSG +menu/SM +meow/DSG +mer/TGDR +mercantile +mercenariness/M +mercenary/SMP +mercer/SM +mercerize/SDG +merchandise/SRDJMZG +merchandiser/M +merchant/SBDMG +merchantability +merchantman/M +merchantmen +merciful/YP +mercifully/U +mercifulness/M +merciless/YP +mercilessness/SM +mercurial/SPY +mercuric +mercury/MS +mercy/SM +mere/YS +meretricious/YP +meretriciousness/SM +merganser/MS +merge/SRDGZ +merger/M +meridian/MS +meridional +meringue/MS +merino/MS +merit/SCGMD +merited/U +meritocracy/MS +meritocratic +meritocrats +meritorious/PY +meritoriousness/MS +merlin/M +mermaid/MS +merman/M +mermen +meromorphic +merrily +merriment/MS +merriness/S +merry/RPT +merrymaker/MS +merrymaking/SM +mes/S +mesa/SM +mescal/SM +mescaline/SM +mesdames/M +mesdemoiselles/M +mesh/GMSD +meshed/U +mesmeric +mesmerism/SM +mesmerize/SRDZG +mesmerized/U +mesmerizer/M +mesomorph/M +mesomorphs +meson/MS +mesosphere/MS +mesozoic +mesquite/MS +mess/GSDM +message/SDMG +messeigneurs +messenger/GSMD +messiah +messiahs +messianic +messieurs/M +messily +messiness/MS +messmate/MS +messy/PRT +mestizo/MS +met/U +meta +metabolic +metabolically +metabolism/MS +metabolite/SM +metabolize/GSD +metacarpal/S +metacarpi +metacarpus/M +metacircular +metacircularity +metal/SGMD +metalanguage/MS +metalization/SM +metalized +metallic/S +metalliferous +metallings +metallography/M +metalloid/M +metallurgic +metallurgical/Y +metallurgist/S +metallurgy/MS +metalsmith/MS +metalwork/RMJGSZ +metalworking/M +metamathematical +metamorphic +metamorphism/SM +metamorphose/GDS +metamorphosis/M +metaphor/MS +metaphoric +metaphorical/Y +metaphosphate/M +metaphysic/SM +metaphysical/Y +metastability/M +metastable +metastases +metastasis/M +metastasize/DSG +metastatic +metatarsal/S +metatarsi +metatarsus/M +metatheses +metathesis/M +metathesized +metathesizes +metathesizing +metavariable +mete/ZDGSR +metempsychoses +metempsychosis/M +meteor/SM +meteoric +meteorically +meteorite/SM +meteoritic/S +meteoritics/M +meteoroid/SM +meteorologic +meteorological +meteorologist/S +meteorology/MS +meter/GDM +methadone/SM +methane/MS +methanol/SM +methinks +methionine/M +method/MS +methodical/YP +methodicalness/SM +methodism +methodist/MS +methodological/Y +methodologists +methodology/MS +methought +methyl/SM +methylated +methylene/M +meticulous/YP +meticulousness/MS +metonymy/M +metric/SM +metrical/Y +metricate/SDNGX +metricize/GSD +metrics/M +metro/SM +metronome/MS +metropolis/SM +metropolitan/S +metropolitanization +mets +mettle/SDM +mettlesome +mew/SGD +mewl/GSD +mews/SM +mezzanine/MS +mezzo/S +mfg +mfr/S +mg +mgr +mi/MNX +miasma/SM +miasmal +mica/MS +mice/M +micelles +mickey/SM +micra's +micro/S +microamp +microanalysis/M +microanalytic +microbe/MS +microbial +microbicidal +microbicide/M +microbiological +microbiologist/MS +microbiology/SM +microbrewery/S +microchemistry/M +microchip/S +microcircuit/MS +microcode/GSD +microcomputer/MS +microcosm/MS +microcosmic +microdensitometer +microdot/MS +microeconomic/S +microeconomics/M +microelectronic/S +microelectronics/M +microfiber/S +microfiche/M +microfilm/DRMSG +microfossils +micrography/M +microgroove/MS +microhydrodynamics +microinstruction/SM +microjoule +microlevel +microlight/S +micromanage/GDSL +micromanagement/S +micrometeorite/MS +micrometeoritic +micrometer/SM +micron/MS +microorganism/SM +microphone/SGM +microprocessing +microprocessor/SM +microprogram/SM +microprogrammed +microprogramming +micros/M +microscope/SM +microscopic +microscopical/Y +microscopy/MS +microsecond/MS +microsimulation/S +microsomal +microstore +microsurgery/SM +microvolt/SM +microwave/BMGSD +microwaveable +microword/S +mid/S +midair/MS +midas +midband/M +midday/MS +midden/SM +middest +middle/GJRSD +middlebrow/SM +middleman/M +middlemen +middlemost +middleweight/SM +middling/Y +middy/SM +midfield/RM +midge/SM +midget/MS +midi/S +midland/MRS +midlife +midlives +midmorn/G +midmost/S +midnight/SYM +midpoint/MS +midrange +midrib/MS +midriff/MS +midscale +midsection/M +midship/S +midshipman/M +midshipmen +midspan +midst/SM +midstream/MS +midsummer/MS +midterm/MS +midtown/MS +midway/S +midweek/SYM +midwicket +midwife/SDMG +midwifery/SM +midwinter/YMS +midwives +midyear/MS +mien/M +miff/GDS +might/S +mightily +mightiness/MS +mightn't +mighty/TPR +mignon +mignonette/SM +migraine/SM +migrant/MS +migrate/ASDG +migration/MS +migrative +migratory/S +mikado/MS +mike/DSMG +mil/MRSZ +milady/MS +milch/M +mild/STYRNP +mildew/DMGS +mildness/MS +mile/SM +mileage/SM +milepost/SM +miler/M +milestone/MS +milieu/SM +militancy/MS +militant/YPS +militantness/M +militarily +militarism/SM +militarist/MS +militaristic +militarization/SCM +militarize/SDCG +military +militate/SDG +militia/SM +militiaman/M +militiamen +milk/GZSRDM +milker/M +milkiness/MS +milkmaid/SM +milkman/M +milkmen +milkshake/S +milksop/SM +milkweed/MS +milky/RPT +mill/SGZMRD +millage/S +millenarian +millenarianism/M +millennial +millennialism +millennium/MS +millepede's +miller/M +millet/MS +milliamp +milliampere/S +milliard/MS +millibar/MS +millidegree/S +milligram/MS +millijoule/S +milliliter/MS +millimeter/SM +milliner/SM +millinery/MS +milling/M +million/HDMS +millionaire/MS +millionth/M +millionths +millipede/SM +millisecond/MS +millivolt/SM +millivoltmeter/SM +milliwatt/S +millpond/MS +millrace/SM +millstone/SM +millstream/SM +millwright/MS +milquetoast/SM +milt/MDSG +mime/DSRMG +mimeograph/GMDS +mimeographs +mimer/M +mimesis/M +mimetic +mimetically +mimic/S +mimicked +mimicker/SM +mimicking +mimicry/MS +mimosa/SM +min/DRZGJ +minaret/MS +minatory +mince/SRDGZJ +mincemeat/MS +mincer/M +mincing/Y +mind's +mind/ARDSZG +mindbogglingly +minded/P +minder/M +mindful/U +mindfully +mindfulness/MS +mindless/YP +mindlessness/SM +mindset/S +mine/SNX +minefield/MS +miner/M +mineral/SM +mineralization/C +mineralized/U +mineralogical +mineralogist/SM +mineralogy/MS +mineshaft +minestrone/MS +minesweeper/MS +mineworkers +mingle/SDG +mini/S +miniature/GMSD +miniaturist/SM +miniaturization/MS +miniaturize/SDG +minibike/S +minibus/SM +minicab/M +minicam/MS +minicomputer/SM +minidress/SM +minify/GSD +minim/SM +minima's +minimal/SY +minimalism/S +minimalist/MS +minimalistic +minimality +minimax/M +minimization/MS +minimize/RSDZG +minimized/U +minimizer/M +minimum/MS +mining/M +minion/M +miniseries +miniskirt/MS +minister/MDGS +ministerial/Y +ministrant/S +ministration/SM +ministry/MS +minivan/S +miniver/M +mink/SM +minke +minnesinger/MS +minnow/SM +minor/DMSG +minority/MS +minotaur/S +minoxidil/S +minster/SM +minstrel/SM +minstrelsy/MS +mint/GZSMRD +mintage/SM +minter/M +minty/RT +minuend/SM +minuet/SM +minus/S +minuscule/SM +minute/RSDPMTYG +minuteman +minutemen +minuteness/SM +minutia/M +minutiae +minx/MS +miracle/MS +miraculous/PY +miraculousness/M +mirage/GSDM +mire/MGDS +mirror/DMGS +mirth/M +mirthful/PY +mirthfulness/SM +mirthless/YP +mirthlessness/M +mirths +miry/RT +mis/SRZ +misaddress/SDG +misadventure/SM +misalign/DSGL +misalignment/MS +misalliance/MS +misanalysed +misandrist +misandry +misanthrope/MS +misanthropic +misanthropically +misanthropist/S +misanthropy/SM +misapplier/M +misapply/GNXRSD +misapprehend/GDS +misapprehension/MS +misappropriate/GNXSD +misbegotten +misbehave/RSDG +misbehaver/M +misbehavior/SM +misbrand/DSG +misc +miscalculate/XGNSD +miscalculation/M +miscall/SDG +miscarriage/MS +miscarry/SDG +miscast/GS +miscegenation/SM +miscellanea +miscellaneous/PY +miscellany/MS +mischance/MGSD +mischief/MDGS +mischievous/PY +mischievousness/MS +miscibility/S +miscible/C +misclassification/M +misclassified +misclassifying +miscode/SDG +miscommunicate/NDS +miscomprehended +misconceive/GDS +misconception/MS +misconduct/GSMD +misconfiguration +misconstruction/MS +misconstrue/DSG +miscopying +miscount/DGS +miscreant/MS +miscue/MGSD +misdeal/SG +misdealt +misdeed/MS +misdemeanant/SM +misdemeanor/SM +misdiagnose/GSD +misdid +misdirect/GSD +misdirection/MS +misdirector/S +misdo/JG +misdoes +misdone +miser/KM +miserable/SP +miserableness/SM +miserably +miserliness/MS +miserly/P +misery/MS +mises/KC +misfeasance/MS +misfeature/M +misfield +misfile/SDG +misfire/SDG +misfit/MS +misfitted +misfitting +misfortune/SM +misgauge/GDS +misgiving/MYS +misgovern/LDGS +misgovernment/S +misguidance/SM +misguide/DRSG +misguided/PY +misguidedness/M +misguider/M +mishandle/SDG +mishap/MS +mishapped +mishapping +mishear/GS +misheard +mishitting +mishmash/SM +misidentification/M +misidentify/GNSD +misinform/GDS +misinformation/SM +misinterpret/RDSZG +misinterpretation/MS +misinterpreter/M +misjudge/DSG +misjudging/Y +misjudgment/MS +mislabel/DSG +mislaid +mislay/GS +mislead/GRJS +misleader/M +misleading/Y +misled +mismanage/LGSD +mismanagement/MS +mismatch/GSD +misname/GSD +misnomer/GSMD +misogamist/MS +misogamy/MS +misogynist/MS +misogynistic +misogynous +misogyny/MS +misperceive/SD +misplace/GLDS +misplacement/MS +misplay/GSD +mispositioned +misprint/SGDM +misprision/SM +mispronounce/DSG +mispronunciation/MS +misquotation/MS +misquote/GDS +misread/RSGJ +misreader/M +misrelated +misremember/DG +misreport/DGS +misrepresent/SDRG +misrepresentation/MS +misrepresenter/M +misroute/DS +misrule/SDG +miss/SDEGV +missal/ESM +misshape/DSG +misshapen/PY +misshapenness/SM +missile/MS +missilery/SM +mission/AMS +missionary/MS +missioned +missioner/SM +missioning +missis's +missive/MS +misspeak/SG +misspecification +misspecified +misspell/SGJD +misspelling/M +misspend/GS +misspent +misspoke +misspoken +misstate/GLDRS +misstatement/MS +misstater/M +misstep/MS +misstepped +misstepping +missus/SM +mist/MRDGZS +mistakable/U +mistake/BMGSR +mistaken/Y +mistaker/M +mistaking/Y +mister/GDM +mistily +mistime/GSD +mistiness/S +mistletoe/MS +mistook +mistral/MS +mistranslated +mistranslates +mistranslating +mistranslation/SM +mistreat/DGSL +mistreatment/SM +mistress/MSY +mistrial/SM +mistrust/SRDG +mistruster/M +mistrustful/Y +misty/PRT +mistype/SDGJ +misunderstand/JSRZG +misunderstander/M +misunderstanding/M +misunderstood +misuse/RSDMG +misuser/M +miswritten +mite/SRMZ +miter/GRDM +miterer/M +mitigate/XNGVDS +mitigated/U +mitigation/M +mitoses +mitosis/M +mitotic +mitt/XSMN +mitten/M +mitzvahs +mix/AGSD +mixable +mixed/U +mixer/SM +mixture/SM +mizzen/MS +mizzenmast/SM +mks +ml +mm +mnemonic/SM +mnemonically +mnemonics/M +mo/CSK +moan/GSZRDM +moat/SMDG +mob/MS +mobbed +mobber +mobbing +mobcap/SM +mobile/S +mobility/MS +mobilizable +mobilization/AMCS +mobilize/CGDS +mobilized/U +mobilizer/MS +mobilizes/A +mobster/MS +moccasin/SM +mocha/SM +mock/GZSRD +mockers/M +mockery/MS +mocking/Y +mockingbird/MS +mod/TSR +modal/Y +modality/MS +mode/MS +model/ZGSJMRD +modeled/A +modeler/M +modeling/M +models/A +modem/SM +moderate/PNGDSXY +moderated/U +moderateness/SM +moderation/M +moderator/MS +modern/PTRYS +modernism/MS +modernist/S +modernistic +modernity/SM +modernization/MS +modernize/SRDGZ +modernized/U +modernizer/M +modernizes/U +modernness/SM +modest/TRY +modesty/MS +modicum/SM +modifiability/M +modifiable/U +modifiableness/M +modification/M +modified/U +modifier/M +modify/NGZXRSD +modish/YP +modishness/MS +modular/SY +modularity/SM +modularization +modularize/SDG +modulate/ADSNCG +modulation/CMS +modulator/ACSM +module/SM +moduli +modulo +modulus/M +modus +mogul/MS +mohair/SM +moiety/MS +moil/SGD +moire/MS +moist/TXPRNY +moisten/ZGRD +moistener/M +moistness/MS +moisture/MS +moisturize/GZDRS +molal +molar/MS +molarity/SM +molasses/MS +mold/MRDJSGZ +moldboard/SM +molder/DG +moldiness/SM +molding/M +moldy/PTR +mole/MTS +molecular/Y +molecularity/SM +molecule/MS +molehill/SM +moleskin/MS +molest/RDZGS +molestation/SM +molested/U +molester/M +moll/MS +mollification/M +mollify/XSDGN +mollusc's +mollusk/S +molly/SM +mollycoddle/SRDG +mollycoddler/M +molt/RDNGZS +molter/M +molybdenite/M +molybdenum/MS +mom/SM +moment/MYS +momenta +momentarily +momentariness/SM +momentary/P +momentous/YP +momentousness/MS +momentum/SM +momma/S +mommy/SM +monad/SM +monadic +monarch/M +monarchic +monarchical +monarchism/MS +monarchist/MS +monarchistic +monarchs +monarchy/MS +monastery/MS +monastic/S +monastical/Y +monasticism/MS +monaural/Y +monetarily +monetarism/S +monetarist/MS +monetary +monetization/CMA +monetize/CGADS +money/SMRD +moneybag/SM +moneychangers +moneyer/M +moneylender/SM +moneymaker/MS +moneymaking/MS +monger/SGDM +mongolism/SM +mongoloid/S +mongoose/SM +mongrel/SM +monies/M +moniker/MS +monism/MS +monist/SM +monition/SM +monitor/GSMD +monitored/U +monitory/S +monk/MS +monkey/SMDG +monkeyshine/S +monkish +monkshood/SM +mono/MS +monochromatic +monochromator +monochrome/MS +monocle/SDM +monoclinic +monoclonal/S +monocotyledon/SM +monocotyledonous +monocular/SY +monodic +monodist/S +monody/MS +monogamist/MS +monogamous/PY +monogamy/MS +monogram/MS +monogrammed +monogramming +monograph/GMDS +monographs +monolingual/S +monolingualism +monolith/M +monolithic +monolithically +monoliths +monologist/S +monologue/GMSD +monomania/MS +monomaniac/MS +monomaniacal +monomer/SM +monomeric +monomial/SM +mononuclear +mononucleoses +mononucleosis/M +monophonic +monoplane/MS +monopole/S +monopolist/MS +monopolistic +monopolization/MS +monopolize/GZDSR +monopolized/U +monopolizes/U +monopoly/MS +monorail/SM +monostable +monosyllabic +monosyllable/MS +monotheism/SM +monotheist/S +monotheistic +monotone/SDMG +monotonic +monotonically +monotonicity +monotonous/YP +monotonousness/MS +monotony/MS +monovalent +monoxide/SM +monseigneur +monsieur/M +monsignor/S +monsoon/MS +monsoonal +monster/SM +monstrance/ASM +monstrosity/SM +monstrous/YP +monstrousness/M +montage/SDMG +month/MY +monthly/S +months +monument/DMSG +monumental/Y +monumentality/M +moo/GSD +mooch/ZSRDG +mood/MS +moodily +moodiness/MS +moody/PTR +moon/GDMS +moonbeam/SM +moonless +moonlight/GZDRMS +moonlighting/M +moonlit +moonscape/MS +moonshine/SRZM +moonshiner/M +moonshot/MS +moonstone/SM +moonstruck +moonwalk/SDG +moor/GDMJS +mooring/M +moorland/MS +moose/M +moot/RDGS +mop/SZGMDR +mope/S +moped/MS +moper/M +mopey +mopier +mopiest +mopish +mopped +moppet/MS +mopping +moraine/MS +moral/SMY +morale/MS +moralist/MS +moralistic +moralistically +morality/UMS +moralization/CS +moralize/CGDRSZ +moralled +moraller +moralling +morass/SM +moratorium/SM +moray/SM +morbid/YP +morbidity/SM +morbidness/S +mordancy/MS +mordant/GDYS +more/DSN +morel/SM +moreover +morgen/M +morgue/SM +moribund/Y +moribundity/M +morion/M +morn/SGJDM +morning/MY +morocco/SM +moron/SM +moronic +moronically +morose/YP +moroseness/MS +morph/GDJ +morpheme/DSMG +morphemic/S +morphia/S +morphine/MS +morphism/MS +morphologic +morphological/Y +morphology/MS +morphophonemic/S +morphophonemics/M +morphs +morris +morrow/MS +morsel/GMDS +mortal/SY +mortality/SM +mortar/GSDM +mortarboard/SM +mortgage/MGDS +mortgageable +mortgagee/SM +mortgagor/SM +mortice's +mortician/SM +mortification/M +mortified/Y +mortifier/M +mortify/DRSXGN +mortise/MGSD +mortuary/MS +mos/S +mosaic/MS +mosaicked +mosaicking +mosey/SGD +mosque/SM +mosquito/M +mosquitoes +moss/SDMG +mossback/MS +mossy/SRT +most/SY +mot/MSV +mote's +mote/ASCNK +motel/MS +motet/SM +moth/ZMR +mothball/DMGS +mother/RDYMZG +motherboard/MS +motherfucker/MS! +motherfucking/! +motherhood/SM +mothering/M +motherland/SM +motherless +motherliness/MS +motherly/P +moths +motif/MS +motile/S +motility/MS +motion's/ACK +motion/GRDMS +motional/K +motioner/M +motionless/YP +motionlessness/S +motions/K +motivate/XDSNGV +motivated/U +motivation/M +motivational/Y +motivator/S +motive/MGSD +motiveless +motley/S +motlier +motliest +motocross/SM +motor/DMSG +motorbike/SDGM +motorboat/MS +motorcade/MSDG +motorcar/MS +motorcycle/GMDS +motorcyclist/SM +motoring/M +motorist/SM +motorization/SM +motorize/DSG +motorized/U +motorman/M +motormen +motormouth +motormouths +motorway/SM +mottle/GSRD +mottler/M +motto/M +mottoes +moue/DSMG +moulder/DSG +moult/GSD +mound/GMDS +mount/EGACD +mountable +mountain/SM +mountaineer/JMDSG +mountaineering/M +mountainous/PY +mountainousness/M +mountainside/MS +mountaintop/SM +mountebank/SGMD +mounted/U +mounter/SM +mounties +mounting/MS +mounts/AE +mourn/ZGSJRD +mourner/M +mournful/YP +mournfuller +mournfullest +mournfulness/S +mourning/M +mouse/SRDGMZ +mouser/M +mousetrap/SM +mousetrapped +mousetrapping +mousiness/MS +mousing/M +mousse/MGSD +mousy/PRT +mouth/MSRDG +mouthful/MS +mouthiness/SM +mouthorgan +mouthpiece/SM +mouths +mouthwash/SM +mouthwatering +mouthy/PTR +mouton/SM +movable/ASP +movableness/AM +move/ARSDGZB +moved/U +movement/SM +mover/AM +movie/SM +moviegoer/S +moving/YS +mow/SDRZG +mower/M +mowing/M +moxie/MS +mozzarella/MS +mp +mpg +mph +ms +mt +mtg +mtge +mu/M +much/SP +muchness/M +mucilage/MS +mucilaginous +muck/GRDMS +mucker/M +muckrake/ZMDRSG +muckraker/M +mucky/RT +mucosa/M +mucous +mucus/SM +mud/MS +mudded +muddily +muddiness/SM +mudding +muddle/GRSDZ +muddlehead/SMD +muddleheaded/P +muddler/M +muddy/TPGRSD +mudflat/S +mudguard/SM +mudlarks +mudroom/S +mudslide/S +mudsling/JRGZ +mudslinger/M +mudslinging/M +muenster/MS +muesli/M +muezzin/MS +muff/GDMS +muffin/SM +muffle/ZRSDG +muffler/M +mufti/MS +mug/SM +mugged +mugger/SM +mugginess/S +mugging/S +muggy/RPT +mugshot/S +mugwump/MS +mukluk/SM +mulatto/M +mulattoes +mulberry/MS +mulch/GMSD +mulct/SDG +mule/MGDS +muleskinner/S +muleteer/MS +mulish/YP +mulishness/MS +mull/RDSG +mullah/M +mullahs +mullein/MS +muller/M +mullet/MS +mulligan/SM +mulligatawny/SM +mullion/MDSG +multi +multicellular +multichannel/M +multicollinearity/M +multicolor/SDM +multicolumn +multicomponent +multicomputer/MS +multicultural +multiculturalism/S +multidimensional +multidimensionality +multidisciplinary +multifaceted +multifamily +multifarious/YP +multifariousness/SM +multifigure +multiform +multifunction/D +multilateral/Y +multilayer +multilevel/D +multilingual +multilingualism/S +multimedia/S +multimegaton/M +multimeter/M +multimillionaire/SM +multinational/S +multinomial/M +multiphase +multiple/SM +multiplet/SM +multiplex/GZMSRD +multiplexor's +multipliable +multiplicand/SM +multiplication/M +multiplicative/YS +multiplicity/MS +multiplier/M +multiply/ZNSRDXG +multiprocess/G +multiprocessor/MS +multiprogram +multiprogrammed +multiprogramming/MS +multipurpose +multiracial +multistage +multistory/S +multisyllabic +multitasking/S +multitude/MS +multitudinous/YP +multitudinousness/M +multiuser +multivalent +multivalued +multivariate +multiversity/M +multivitamin/S +mum/MS +mumble/ZJGRSD +mumbler/M +mumbletypeg/S +mummed +mummer/SM +mummery/MS +mummification/M +mummify/XSDGN +mumming +mummy/GSDM +mumps/M +mun/S +munch/ZRSDG +muncher/M +munchies +mundane/YSP +munge/JGZSRD +municipal/YS +municipality/SM +munificence/MS +munificent/Y +munition/SDG +muon/M +mural/SM +muralist/SM +murder/GZRDMS +murderer/M +murderess/S +murderous/YP +murderousness/M +muriatic +murk/TRMS +murkily +murkiness/S +murky/RPT +murmur/RDMGZSJ +murmurer/M +murmuring/U +murmurous +murrain/SM +mus/GJDSR +muscat/SM +muscatel/MS +muscle/SDMG +musclebound +muscovite/MS +muscular/Y +muscularity/SM +musculature/SM +muse +muser/M +musette/SM +museum/MS +mush/MSRDG +musher/M +mushiness/MS +mushroom/DMSG +mushy/PTR +music/SM +musical/YU +musicale/SM +musicality/SM +musicals +musician/MYS +musicianship/MS +musicked +musicking +musicological +musicologist/MS +musicology/MS +musing/Y +musk/GDMS +muskeg/SM +muskellunge/SM +musket/SM +musketeer/MS +musketry/MS +muskie/M +muskiness/MS +muskmelon/MS +muskox/N +muskrat/MS +musky/RSPT +muslin/MS +muss/SDG +mussel/MS +mussy/RT +must've +must/RDGZS +mustache/DSM +mustachio/MDS +mustang/MS +mustard/MS +muster/GD +mustily +mustiness/MS +mustn't +musty/RPT +mutability/SM +mutable/P +mutableness/M +mutably +mutagen/SM +mutant/MS +mutate/XVNGSD +mutation/M +mutational/Y +mutator/S +mute/PDSRBYTG +muted/Y +muteness/S +mutilate/XDSNG +mutilation/M +mutilator/MS +mutineer/SMDG +mutinous/Y +mutiny/MGSD +mutt/ZSMR +mutter/GZRDJ +mutterer/M +mutton/SM +muttonchops +mutual/SY +mutuality/S +muumuu/MS +muzak +muzzle/MGRSD +muzzled/U +muzzler/M +my/S +mycologist/MS +mycology/MS +myelitides +myelitis/M +myers +mylar +myna/SM +myocardial +myocardium/M +myopia/MS +myopic/S +myopically +myriad/S +myrmidon/S +myrrh/M +myrrhs +myrtle/SM +mys +myself +mysterious/YP +mysteriousness/MS +mystery/MDSG +mystic/SM +mystical/Y +mysticism/MS +mystification/M +mystifier/M +mystify/CSDGNX +mystifying/Y +mystique/MS +myth/MS +mythic +mythical/Y +mythographer/SM +mythography/M +mythological/Y +mythologist/MS +mythologize/CSDG +mythology/SM +myths +métier/S +mêlée/MS +n's/CI +n/T +nab/S +nabbed +nabbing +nabob/SM +nacelle/SM +nacho/S +nacre/MS +nacreous +nadir/SM +nae/VM +nag/MS +nagged +nagger/S +nagging/Y +naiad/SM +naifs +nail/SGMRD +nailbrush/SM +nailer/M +naive/SRTYP +naivety/MS +naiveté/SM +naked/TYRP +nakedness/MS +name's +name/ADSG +nameable/U +named's +named/U +namedrop +namedropping +nameless/PY +namely +nameplate/MS +namer/SM +namesake/SM +naming/M +nanny/SDMG +nanometer/MS +nanosecond/SM +nap/SM +napalm/MDGS +nape/SM +naphtha/SM +naphthalene/MS +napkin/SM +napless +napoleon/MS +napped +napper/MS +napping +nappy/TRSM +narc/DGS +narcissism/MS +narcissist/MS +narcissistic +narcissus/M +narcoleptic +narcoses +narcosis/M +narcotic/SM +narcotization/S +narcotize/GSD +nark's +narrate/VGNSDX +narration/M +narrative/MYS +narratology +narrator/SM +narrow/RDYTGPS +narrowing/P +narrowness/SM +narwhal/MS +nary +nasal/YS +nasality/MS +nasalization/MS +nasalize/GDS +nascence/ASM +nascent/A +nastily +nastiness/MS +nasturtium/SM +nasty/TRSP +natal +natalist +natality/M +natch/S +nation/MS +national/YS +nationalism/SM +nationalist/MS +nationalistic +nationalistically +nationality/MS +nationalization/MS +nationalize/CSDG +nationalized/AU +nationalizer/SM +nationhood/SM +nationwide +native/PYS +nativeness/M +nativity/MS +natl +natter/SGD +nattily +nattiness/SM +natty/TRP +natural/PUY +naturalism/MS +naturalist/MS +naturalistic +naturalization/SM +naturalize/GSD +naturalized/U +naturalness/US +naturals +nature's +nature/ASDCG +naturist +naught/MS +naughtily +naughtiness/SM +naughty/TPRS +nausea/SM +nauseate/DSG +nauseating/Y +nauseous/P +nauseousness/SM +nautical/Y +nautilus/MS +naval/Y +nave/SM +navel/MS +navigability/SM +navigable/P +navigableness/M +navigate/DSXNG +navigation/M +navigational +navigator/MS +navvy/M +navy/SM +nay/MS +naysayer/S +ne'er +neap/DGS +near/TYRDPSG +nearby +nearly/RT +nearness/MS +nearside/M +nearsighted/YP +nearsightedness/S +neat/YRNTXPS +neaten/DG +neath +neatness/MS +nebula/M +nebulae +nebular +nebulous/PY +nebulousness/SM +necessaries +necessarily/U +necessary/U +necessitate/DSNGX +necessitation/M +necessitous +necessity/SM +neck/GRDMJS +neckband/M +neckerchief/MS +necking/M +necklace/DSMG +neckline/MS +necktie/MS +necrology/SM +necromancer/MS +necromancy/MS +necromantic +necrophilia/M +necrophiliac/S +necropolis/SM +necropsy/M +necroses +necrosis/M +necrotic +nectar/SM +nectarine/SM +nectarous +nectary/MS +need/YRDGS +needed/U +needer/M +needful/YSP +neediness/MS +needle/GMZRSD +needlecraft/M +needlepoint/SM +needless/YP +needlessness/S +needlewoman/M +needlewomen +needlework/RMS +needn't +needy/TPR +nefarious/YP +nefariousness/MS +neg/S +negate/XRSDVNG +negated/U +negater/M +negation/M +negative/PDSYG +negativeness/SM +negativism/MS +negativity/MS +negator/MS +neglect/SDRG +neglecter/M +neglectful/YP +neglectfulness/SM +negligee/SM +negligence/MS +negligent/Y +negligibility/M +negligible +negligibly +negotiability/MS +negotiable/A +negotiant/M +negotiate/ASDXGN +negotiation/MA +negotiator/MS +negritude/MS +negroid +neigh/MDG +neighbor/SMRDYZGJ +neighbored/U +neighborer/M +neighborhood/SM +neighborliness/UM +neighborlinesses +neighborly/UP +neighs +neither +nelson/MS +nematic +nematode/SM +nemeses +nemesis +neoclassic/M +neoclassical +neoclassicism/MS +neocolonialism/MS +neocortex/M +neodymium/MS +neolithic +neologism/SM +neomycin/M +neon/DMS +neonatal/Y +neonate/MS +neophyte/MS +neoplasm/SM +neoplastic +neoprene/SM +nepenthe/MS +nephew/MS +nephrite/SM +nephritic +nephritides +nephritis/M +nepotism/MS +nepotist/S +neptunium/MS +nerd/S +nerdy/RT +nerve's +nerve/UGSD +nerveless/YP +nervelessness/SM +nerviness/SM +nerving/M +nervous/PY +nervousness/SM +nervy/TPR +nest/RDGSBM +nester/M +nestle/RSDG +nestler/M +nestling/M +net/SM +netball/M +nether +nethermost +netherworld/S +nett/JGRDS +netting/M +nettle/MSDG +nettlesome +network/SJMDG +neural/Y +neuralgia/MS +neuralgic +neurasthenia/MS +neurasthenic/S +neuritic/S +neuritides +neuritis/M +neuroanatomy +neurobiology/M +neurological/Y +neurologist/MS +neurology/SM +neuromuscular +neuron/MS +neuronal +neurone/S +neuropathology/M +neurophysiology/M +neuropsychiatric +neuroses +neurosis/M +neurosurgeon/MS +neurosurgery/SM +neurotic/S +neurotically +neurotransmitter/S +neut/ZR +neuter/JZGRD +neutral/PYS +neutralise's +neutralism/MS +neutralist/S +neutrality/MS +neutralization/MS +neutralize/GZSRD +neutralized/U +neutrino/MS +neutron/MS +never +nevermore +nevertheless +nevi +nevus/M +new/SPTGDRY +newbie/S +newborn/S +newcomer/MS +newed/A +newel/MS +newer/A +newfangled +newfound +newfoundland +newish +newline/SM +newlywed/MS +newness/MS +news's +news/A +newsagent/MS +newsboy/SM +newscast/SRMGZ +newscaster/M +newscasting/M +newsdealer/MS +newsed +newses +newsflash/S +newsgirl/S +newsgroup/SM +newsing +newsletter/SM +newsman/M +newsmen +newspaper/SMGD +newspaperman/M +newspapermen +newspaperwoman/M +newspaperwomen +newsprint/MS +newsreader/MS +newsreel/SM +newsroom/S +newsstand/MS +newsweekly/S +newswire +newswoman/M +newswomen +newsworthiness/SM +newsworthy/RPT +newsy/TRS +newt/MS +newton/SM +next +nexus/SM +niacin/SM +nib/SM +nibbed +nibbing +nibble/RSDGZ +nibbler/M +nice/YTPR +niceness/MS +nicety/MS +niche/SDGM +nichrome +nick/GZRDMS +nickel/SGMD +nickelodeon/SM +nicker/GD +nicknack's +nickname/MGDRS +nicknamer/M +nicotine/MS +niece/MS +nifty/TRS +niggard/SGMDY +niggardliness/SM +niggardly/P +nigger/SGDM! +niggle/RSDGZJ +niggler/M +niggling/Y +nigh/RDGT +nighs +night/SMYDZ +nightcap/SM +nightclothes +nightclub/MS +nightclubbed +nightclubbing +nightdress/MS +nightfall/SM +nightgown/MS +nighthawk/MS +nightie/MS +nightingale/SM +nightlife/MS +nightlong +nightmare/MS +nightmarish/Y +nightshade/SM +nightshirt/MS +nightspot/MS +nightstand/SM +nightstick/S +nighttime/S +nightwear/M +nighty's +nihilism/MS +nihilist/MS +nihilistic +nil/MYS +nilled +nilling +nilpotent +nimbi +nimble/TRP +nimbleness/SM +nimbly +nimbus/DM +nincompoop/MS +nine/MS +ninefold +ninepence/M +ninepin/S +ninepins/M +nineteen/SMH +nineteenths +ninetieths +ninety/MHS +ninja/S +ninny/SM +ninth +ninths +niobium/MS +nip/S +nipped +nipper/DMGS +nippiness/S +nipping/Y +nipple/GMSD +nippy/TPR +nirvana/MS +nisei +nit/ZSMR +niter/M +nitpick/DRSJZG +nitrate/MGNXSD +nitration/M +nitric +nitride/MGS +nitriding/M +nitrification/SM +nitrite/MS +nitrocellulose/MS +nitrogen/SM +nitrogenous +nitroglycerin/MS +nitrous +nitwit/MS +nix/GDSR +nixer/M +nm +no/A +nob/MY +nobelium/MS +nobility/MS +noble/TPSR +nobleman/M +noblemen +nobleness/SM +noblesse/M +noblewoman +noblewomen +nobody/MS +nocturnal/SY +nocturne/SM +nod/SM +nodal/Y +nodded +nodding +noddle/MSDG +noddy/M +node/MS +nodular +nodule/SM +noel/S +noes/S +noggin/SM +nohow +noise/GMSD +noiseless/YP +noiselessness/SM +noisemake/ZGR +noisemaker/M +noisily +noisiness/MS +noisome +noisy/TPR +nomad/SM +nomadic +nomenclature/MS +nominal/K +nominalized +nominally +nominals +nominate/CDSAXNG +nomination/MAC +nominative/SY +nominator/CSM +nominee/MS +non +nonabrasive +nonabsorbent/S +nonacademic/S +nonacceptance/MS +nonacid/MS +nonactive +nonadaptive +nonaddictive +nonadhesive +nonadjacent +nonadjustable +nonadministrative +nonage/MS +nonagenarian/MS +nonaggression/SM +nonagricultural +nonalcoholic/S +nonaligned +nonalignment/SM +nonallergic +nonappearance/MS +nonassignable +nonathletic +nonattendance/SM +nonautomotive +nonavailability/SM +nonbasic +nonbeliever/SM +nonbelligerent/S +nonblocking +nonbreakable +nonburnable +nonbusiness +noncaloric +noncancerous +noncarbohydrate/M +nonce/MS +nonchalance/SM +nonchalant/YP +nonchargeable +nonclerical/S +nonclinical +noncollectable +noncom/MS +noncombatant/MS +noncombustible/S +noncommercial/S +noncommissioned +noncommittal/Y +noncommunicable +noncompeting +noncompetitive +noncompliance/MS +noncomplying/S +noncomprehending +nonconducting +nonconductor/MS +nonconforming +nonconformist/SM +nonconformity/SM +nonconsecutive +nonconservative +nonconstructive +noncontagious +noncontiguous +noncontinuous +noncontributing +noncontributory +noncontroversial +nonconvertible +noncooperation/SM +noncorroding/S +noncorrosive +noncredit +noncriminal/S +noncritical +noncrystalline +noncumulative +noncustodial +noncyclic +nondairy +nondecreasing +nondeductible +nondelivery/MS +nondemocratic +nondenominational +nondepartmental +nondepreciating +nondescript/YS +nondestructive/Y +nondetachable +nondeterminacy +nondeterminate/Y +nondeterminism +nondeterministic +nondeterministically +nondisciplinary +nondisclosure/SM +nondiscrimination/SM +nondiscriminatory +nondramatic +nondrinker/SM +nondrying +nondurable +none/S +noneconomic +noneducational +noneffective/S +nonelastic +nonelectric/S +nonelectrical +nonemergency +nonempty +nonenforceable +nonentity/MS +nonequivalence/M +nonequivalent/S +nones/M +nonessential/S +nonesuch/SM +nonetheless +nonevent/MS +nonexchangeable +nonexclusive +nonexempt +nonexistence/MS +nonexistent +nonexplosive/S +nonextensible +nonfactual +nonfading +nonfat +nonfatal +nonfattening +nonferrous +nonfiction/SM +nonfictional +nonflammable +nonflowering +nonfluctuating +nonflying +nonfood/M +nonfreezing +nonfunctional +nongovernmental +nongranular +nonhazardous +nonhereditary +nonhuman +nonidentical +noninclusive +nonindependent +nonindustrial +noninfectious +noninflammatory +noninflationary +noninflected +nonintellectual/S +noninteracting +noninterchangeable +noninterference/MS +nonintervention/SM +nonintoxicating +nonintuitive +noninvasive +nonionic +nonirritating +nonjudgmental +nonjudicial +nonlegal +nonlethal +nonlinear/Y +nonlinearity/MS +nonlinguistic +nonliterary +nonliving +nonlocal +nonmagical +nonmagnetic +nonmalignant +nonmember/SM +nonmetal/MS +nonmetallic +nonmigratory +nonmilitant/S +nonmilitary +nonnarcotic/S +nonnative/S +nonnegative +nonnegotiable +nonnuclear +nonnumerical/S +nonobjective +nonobligatory +nonobservance/MS +nonobservant +nonoccupational +nonoccurence +nonofficial +nonogenarian +nonoperational +nonoperative +nonorthogonal +nonorthogonality +nonparallel/S +nonparametric +nonpareil/SM +nonparticipant/SM +nonparticipating +nonpartisan/S +nonpaying +nonpayment/SM +nonperformance/SM +nonperforming +nonperishable/S +nonperson/S +nonperturbing +nonphysical/Y +nonplus/S +nonplussed +nonplussing +nonpoisonous +nonpolitical +nonpolluting +nonporous +nonpracticing +nonprejudicial +nonprescription +nonprocedural/Y +nonproductive +nonprofessional/S +nonprofit/SB +nonprogrammable +nonprogrammer +nonproliferation/SM +nonpublic +nonpunishable +nonracial +nonradioactive +nonrandom +nonreactive +nonreciprocal/S +nonreciprocating +nonrecognition/SM +nonrecoverable +nonrecurring +nonredeemable +nonreducing +nonrefillable +nonrefundable +nonreligious +nonrenewable +nonrepresentational +nonresident/SM +nonresidential +nonresidual +nonresistance/SM +nonresistant/S +nonrespondent/S +nonresponse +nonrestrictive +nonreturnable/S +nonrhythmic +nonrigid +nonsalaried +nonscheduled +nonscientific +nonscoring +nonseasonal +nonsectarian +nonsecular +nonsegregated +nonsense/MS +nonsensical/PY +nonsensicalness/M +nonsensitive +nonsexist +nonsexual +nonsingular +nonskid +nonslip +nonsmoker/SM +nonsmoking +nonsocial +nonspeaking +nonspecialist/MS +nonspecializing +nonspecific +nonspiritual/S +nonstaining +nonstandard +nonstarter/SM +nonstick +nonstop +nonstrategic +nonstriking +nonstructural +nonsuccessive +nonsupervisory +nonsupport/GS +nonsurgical +nonsustaining +nonsympathizer/M +nontarnishable +nontaxable/S +nontechnical/Y +nontenured +nonterminal/MS +nonterminating +nontermination/M +nontheatrical +nonthinking/S +nonthreatening +nontoxic +nontraditional +nontransferable +nontransparent +nontrivial +nontropical +nonuniform +nonunion/S +nonuser/SM +nonvenomous +nonverbal/Y +nonveteran/MS +nonviable +nonviolence/SM +nonviolent/Y +nonvirulent +nonvocal +nonvocational +nonvolatile +nonvolunteer/S +nonvoter/MS +nonvoting +nonwhite/SM +nonworking +nonyielding +nonzero +noodle/GMSD +nook/MS +noon/GDMS +noonday/MS +nooning/M +noontide/MS +noontime/MS +noose/SDGM +nope/S +nor/H +noradrenalin +noradrenaline/M +norm/SMGD +normal/SY +normalcy/MS +normality/SM +normalization's +normalization/A +normalizations +normalize/SRDZGB +normalized/AU +normalizes/AU +normative/YP +normativeness/M +north/MRGZ +northbound +northeast/ZSMR +northeaster/YM +northeastern +northeastward/S +norther/MY +northerly/S +northern/RYZS +northernmost +northing/M +northland +northmen +norths +northward/S +northwest/MRZS +northwester/YM +northwestern +northwestward/S +nos/GDS +nose/M +nosebag/M +nosebleed/SM +nosecone/S +nosed/V +nosedive/DSG +nosegay/MS +nosh/MSDG +nosily +nosiness/MS +nosing/M +nostalgia/SM +nostalgic/S +nostalgically +nostril/SM +nostrum/SM +nosy/SRPMT +not/DRGB +notability/SM +notable/PS +notableness/M +notably +notarial +notarization/S +notarize/DSG +notary/MS +notate/VGNXSD +notation/CMSF +notational/CY +notative/CF +notch/MSDG +note's +note/CSDFG +notebook/MS +noted/YP +notedness/M +notepad/S +notepaper/MS +noteworthiness/SM +noteworthy/P +nothing/PS +nothingness/SM +notice/MSDG +noticeable/U +noticeably +noticeboard/S +noticed/U +notifiable +notification/M +notifier/M +notify/NGXSRDZ +notion/MS +notional/Y +notoriety/S +notorious/YP +notoriousness/M +notwithstanding +nougat/MS +noun/SMK +nourish/DRSGL +nourished/U +nourisher/M +nourishment/SM +nous/M +nouveau +nouvelle +nova/MS +novae +novel/SM +novelette/SM +novelist/SM +novelization/S +novelize/GDS +novella/SM +novelty/MS +novena/SM +novene +novice/MS +novitiate/MS +now/S +nowadays +noway/S +nowhere/S +nowise +noxious/PY +noxiousness/M +nozzle/MS +nroff/M +nth +nu/M +nuance/SDM +nub/MS +nubbin/SM +nubby/RT +nubile +nuclear/K +nuclease/M +nucleate/DSXNG +nucleated/A +nucleation/M +nuclei/M +nucleic +nucleoli +nucleolus/M +nucleon/MS +nucleotide/MS +nucleus/M +nuclide/M +nude/CRS +nudely +nudeness/M +nudest +nudge/GSRD +nudger/M +nudism/MS +nudist/MS +nudity/MS +nugatory +nugget/SM +nuisance/MS +nuke/DSMG +null/DSG +nullification/M +nullifier/M +nullify/RSDXGNZ +nullity/SM +numb/SGZTYRDP +number/RDMGJ +numbered/UA +numberer/M +numberless +numberplate/M +numbers/A +numbing/Y +numbness/MS +numbskull's +numerable/IC +numeracy/SI +numeral/YMS +numerate/SDNGX +numerates/I +numeration/M +numerator/MS +numeric/S +numerical/Y +numerological +numerologist/S +numerology/MS +numerous/YP +numerousness/M +numinous/S +numismatic/S +numismatics/M +numismatist/MS +numskull/SM +nun/MS +nuncio/SM +nunnery/MS +nuptial/S +nurse/SRDJGMZ +nursemaid/MS +nurser/M +nursery/MS +nurseryman/M +nurserymen +nursling/M +nurture/SRDGZM +nurturer/M +nus +nut/MS +nutate/NGSD +nutation/M +nutcrack/RZ +nutcracker/M +nuthatch/SM +nutmeat/SM +nutmeg/MS +nutmegged +nutmegging +nutpick/MS +nutria/SM +nutrient/MS +nutriment/MS +nutrition/SM +nutritional/Y +nutritionist/MS +nutritious/PY +nutritiousness/MS +nutritive/Y +nutshell/MS +nutted +nuttiness/SM +nutting +nutty/TRP +nuzzle/GZRSD +nylon/SM +nymph/M +nymphet/MS +nympholepsy/M +nymphomania/MS +nymphomaniac/S +nymphs +née +o +o'clock +o'er +o's +oaf/MS +oafish/PY +oafishness/S +oak/SMN +oakum/MS +oakwood +oar/GSMD +oarlock/MS +oarsman/M +oarsmen +oarswoman +oarswomen +oases +oasis/M +oat/SMNR +oatcake/MS +oater/M +oath/M +oaths +oatmeal/SM +ob +obbligato/S +obduracy/S +obdurate/PDSYG +obdurateness/S +obedience/EMS +obedient/EY +obeisance/MS +obeisant/Y +obelisk/SM +obese +obesity/MS +obey/EDRGS +obeyer/EM +obfuscate/SRDXGN +obfuscation/M +obfuscatory +obi/MDGS +obit/SMR +obituary/SM +obj +object/SGVMD +objectify/GSDXN +objection/SMB +objectionable/U +objectionableness/M +objectionably +objective/PYS +objectiveness/MS +objectivity/MS +objector/SM +objurgate/GNSDX +objurgation/M +oblate/NYPSX +oblation/M +obligate/NGSDXY +obligation/M +obligational +obligatorily +obligatory +oblige/SRDG +obliged/E +obliger/M +obliges/E +obliging/PY +obligingness/M +oblique/DSYGP +obliqueness/S +obliquity/MS +obliterate/VNGSDX +obliteration/M +obliterative/Y +oblivion/MS +oblivious/YP +obliviousness/MS +oblong/SYP +oblongness/M +obloquies +obloquy/M +obnoxious/YP +obnoxiousness/MS +oboe/SM +oboist/S +obos +obs +obscene/RYT +obscenity/MS +obscurantism/MS +obscurantist/MS +obscuration +obscure/YTPDSRGL +obscureness/M +obscurity/MS +obsequies +obsequious/YP +obsequiousness/S +obsequy +observability/M +observable/SU +observably +observance/MS +observant/U +observantly +observants +observation/MS +observational/Y +observatory/MS +observe/ZGDSRB +observed/U +observer/M +observing/Y +obsess/GVDS +obsession/MS +obsessional +obsessive/PYS +obsessiveness/S +obsidian/SM +obsolesce/GSD +obsolescence/S +obsolescent/Y +obsolete/GPDSY +obsoleteness/M +obstacle/SM +obstetric/S +obstetrical +obstetrician/SM +obstetrics/M +obstinacy/SM +obstinate/PY +obstinateness/M +obstreperous/PY +obstreperousness/SM +obstruct/RDVGS +obstructed/U +obstructer/M +obstruction/SM +obstructionism/SM +obstructionist/MS +obstructive/PSY +obstructiveness/MS +obtain/LSGDRB +obtainable/U +obtainably +obtainment/S +obtrude/DSRG +obtruder/M +obtrusion/S +obtrusive/UPY +obtrusiveness/MSU +obtuse/PRTY +obtuseness/S +obverse/YS +obviate/XGNDS +obvious/YP +obviousness/SM +ocarina/MS +occasion/MDSJG +occasional/Y +occident/M +occidental/SY +occipital/Y +occlude/GSD +occlusion/MS +occlusive/S +occult/SRDYG +occulter/M +occultism/SM +occupancy/SM +occupant/MS +occupation/SAM +occupational/Y +occupied/AU +occupier/M +occupies/A +occupy/RSDZG +occur/AS +occurred/A +occurrence/SM +occurring/A +ocean/MS +oceanfront/MS +oceangoing +oceanic +oceanographer/SM +oceanographic +oceanography/SM +oceanology/MS +oceanside +ocelot/SM +ocher/DMGS +octagon/SM +octagonal/Y +octahedral +octahedron/M +octal/S +octane/MS +octant/M +octave/MS +octavo/MS +octennial +octet/SM +octile +octillion/M +octogenarian/MS +octopi +octopus/SM +octoroon/M +ocular/S +oculist/SM +odalisque/SM +odd/TRYSPL +oddball/SM +oddity/MS +oddment/MS +oddness/MS +ode/MDRS +odious/PY +odiousness/MS +odium/MS +odometer/SM +odor/DMS +odoriferous +odorless +odorous/YP +odyssey/S +oedipal +oenology/MS +oenophile/S +oesophagi +oeuvre/SM +of/K +off/SZGDRJ +offal/MS +offbeat/MS +offcuts +offend/SZGDR +offender/M +offense/MSV +offensive/YSP +offensively/I +offensiveness/MSI +offer/RDJGZ +offerer/M +offering/M +offertory/SM +offhand/D +offhanded/YP +offhandedness/S +office/SRMZ +officeholder/SM +officemate/S +officer/GMD +officership/S +official/PSYM +officialdom/SM +officialism/SM +officially/U +officiant/SM +officiate/XSDNG +officiation/M +officiator/MS +officio +officious/YP +officiousness/MS +offing/M +offish +offload/GDS +offprint/GSDM +offramp +offset/SM +offsetting +offshoot/MS +offshore +offside/RS +offspring/M +offstage/S +offtrack +oft/NRT +often/RT +oftentimes +ofttimes +ogive/M +ogle/ZGDSR +ogre/MS +ogreish +ogress/S +oh +ohm/SM +ohmic +ohmmeter/MS +oho/S +ohs +oil/MDRSZG +oilcloth/M +oilcloths +oiler/M +oilfield/MS +oiliness/SM +oilman/M +oilmen +oilseed/SM +oilskin/MS +oily/TPR +oink/GDS +ointment/SM +okapi/SM +okay/M +okra/MS +old/XTNRPS +olden/DG +oldie/MS +oldish +oldness/S +oldster/SM +oleaginous +oleander/SM +olefin/M +oleo/S +oleomargarine/SM +oles +olfactory +oligarch/M +oligarchic +oligarchical +oligarchs +oligarchy/SM +oligopolistic +oligopoly/MS +olive/MSR +olé +om/XN +ombudsman/M +ombudsmen +omega/MS +omelet/SM +omelette's +omen/DMG +omicron/MS +ominous/YP +ominousness/SM +omission/MS +omit/S +omitted +omitting +omni/M +omnibus/MS +omnipotence/SM +omnipotent/SY +omnipresence/MS +omnipresent/Y +omniscience/SM +omniscient/YS +omnivore/MS +omnivorous/PY +omnivorousness/MS +oms +on/RY +onanism/M +once/SR +oncer/M +oncogene/S +oncologist/S +oncology/SM +oncoming/S +one/NPMSX +oneiric +oneiric's +oneness/MS +oner/M +onerous/YP +onerousness/SM +oneself +onetime +oneupmanship +ongoing/S +onion/GDM +onionskin/MS +onlooker/MS +onlooking +only/TP +onomatopoeia/SM +onomatopoeic +onomatopoetic +onrush/GMS +ons +onset/SM +onsetting +onshore +onside +onslaught/MS +onto +ontogeny/SM +ontological/Y +ontology/SM +onus/SM +onward/S +onyx/MS +oodles +ooh/GD +oohs +oolitic +oops/S +ooze/GDS +oozy/RT +op/XGDN +opacity/SM +opal/SM +opalescence/S +opalescent/Y +opaque/GTPYRSD +opaqueness/SM +opcode/MS +ope/S +open/YRDJGZTP +opencast +opened/AU +opener/M +openhanded/P +openhandedness/SM +openhearted +opening/M +openness/S +opens/A +openwork/MS +opera/SM +operable/I +operand/SM +operandi +operant/YS +operate/XNGVDS +operatic/S +operatically +operation/M +operational/Y +operationalization/S +operationalize/D +operative/IP +operatively +operativeness/MI +operatives +operator/SM +operetta/MS +ophthalmic/S +ophthalmologist/SM +ophthalmology/MS +opiate/GMSD +opine/XGNSD +opinion/M +opinionated/PY +opinionatedness/M +opioid +opium/MS +opossum/SM +opp +opponent/MS +opportune/IY +opportunism/SM +opportunist/SM +opportunistic +opportunistically +opportunity/MS +oppose/BRSDG +opposed/U +opposer/M +opposite/SXYNP +oppositeness/M +opposition/M +oppositional +oppress/DSGV +oppression/MS +oppressive/YP +oppressiveness/MS +oppressor/MS +opprobrious/Y +opprobrium/SM +ops +opt/DSG +opthalmic +opthalmologic +opthalmology +optic/S +optical/Y +optician/SM +optics/M +optima +optimal/Y +optimality +optimise's +optimism/SM +optimist/SM +optimistic +optimistically +optimization/SM +optimize/DRSZG +optimized/U +optimizer/M +optimizes/U +optimum/SM +option/GDMS +optional/YS +optionality/M +optoelectronic +optometric +optometrist/MS +optometry/SM +opulence/SM +opulent/Y +opus/SM +or/MY +oracle/GMSD +oracular +oral/YS +orange/MS +orangeade/MS +orangery/SM +orangutan/MS +orate/SDGNX +oration/M +orator/MS +oratorical/Y +oratorio/MS +oratory/MS +orb/SMDG +orbicular +orbiculares +orbit/MRDGZS +orbital/MYS +orchard/SM +orchestra/MS +orchestral/Y +orchestrate/GNSDX +orchestrater's +orchestration/M +orchestrator/M +orchid/SM +ordain/SGLDR +ordainer/M +ordainment/MS +ordeal/SM +order's/E +order/AESGD +ordered/U +orderer +ordering/S +orderless +orderliness/SE +orderly/PS +ordinal/S +ordinance/MS +ordinarily +ordinariness/S +ordinary/RSPT +ordinate's +ordinate/I +ordinated +ordinates +ordinating +ordination/SM +ordnance/SM +ordure/MS +ore/NSM +oregano/SM +organ/MS +organdie's +organdy/MS +organelle/MS +organic/S +organically/I +organism/MS +organismic +organist/MS +organizable/UMS +organization/MEAS +organizational/MYS +organize/AGZDRS +organized/UE +organizer/MA +organizes/E +organizing/E +organometallic +organza/SM +orgasm/GSMD +orgasmic +orgiastic +orgy/SM +oriel/MS +orient's +orient/GADES +orientable +oriental/SY +orientate/ESDXGN +orientated/A +orientates/A +orientation/AMES +orienteering/M +orienter +orifice/MS +orig +origami/MS +origin/MS +original/US +originality/SM +originally +originate/VGNXSD +origination/M +originative/Y +originator/SM +oriole/SM +orison/SM +ormolu/SM +ornament/GSDM +ornamental/SY +ornamentation/SM +ornate/YP +ornateness/SM +orneriness/SM +ornery/PRT +ornithological +ornithologist/SM +ornithology/MS +orographic/M +orography/M +orotund +orotundity/MS +orphan/SGDM +orphanage/MS +orphanhood/M +orris/SM +ors +orthodontia/S +orthodontic/S +orthodontics/M +orthodontist/MS +orthodox/YS +orthodoxies +orthodoxly/U +orthodoxy's +orthodoxy/U +orthogonal/Y +orthogonality/M +orthogonalization/M +orthogonalized +orthographic +orthographically +orthography/MS +orthonormal +orthopedic/S +orthopedics/M +orthopedist/SM +orthophosphate/MS +orthorhombic +oscillate/SDXNG +oscillation/M +oscillator/SM +oscillatory +oscilloscope/SM +osculate/XDSNG +osculation/M +osier/MS +osmium/MS +osmoses +osmosis/M +osmotic +osprey/SM +osseous/Y +ossification/M +ossify/NGSDX +ostensible +ostensibly +ostentation/MS +ostentatious/PY +ostentatiousness/M +osteoarthritides +osteoarthritis/M +osteology/M +osteopath/M +osteopathic +osteopaths +osteopathy/MS +osteoporoses +osteoporosis/M +ostracise's +ostracism/MS +ostracize/GSD +ostrich/MS +other/SMP +otherness/M +otherwise +otherworld/Y +otherworldly/P +otiose +otter/DMGS +ottoman/MS +oubliette/SM +ouch/SDG +ought/SGD +oughtn't +ounce/MS +our/S +ourself +ourselves +oust/RDGZS +ouster/M +out/PJZGSDR +outage/MS +outargue/GDS +outback/MRS +outbalance/GDS +outbid/S +outbidding +outboard/S +outboast/GSD +outbound/S +outbreak/SMG +outbroke +outbroken +outbuilding/SM +outburst/MGS +outcast/GSM +outclass/SDG +outcome/SM +outcrop/SM +outcropped +outcropping/S +outcry/MSDG +outdated/P +outdid +outdistance/GSD +outdo/G +outdoes +outdone +outdoor/S +outdoorsy +outdraw/GS +outdrawn +outdrew +outermost +outerwear/M +outface/SDG +outfall/MS +outfield/RMSZ +outfielder/M +outfight/SG +outfit/MS +outfitted +outfitter/MS +outfitting +outflank/SGD +outflow/SMDG +outfought +outfox/GSD +outgeneraled +outgo/GJ +outgoes +outgoing/P +outgrew +outgrip +outgrow/GSH +outgrown +outgrowth/M +outgrowths +outguess/SDG +outhit/S +outhitting +outhouse/SM +outing/M +outlaid +outland/ZR +outlander/M +outlandish/PY +outlandishness/MS +outlast/GSD +outlaw/SDMG +outlawry/M +outlay/GSM +outlet/SM +outliers +outline/SDGM +outlive/GSD +outlook/MDGS +outlying +outmaneuver/GSD +outmatch/SDG +outmigration +outmoded +outness/M +outnumber/GDS +outpaced +outpatient/SM +outperform/DGS +outplacement/S +outplay/GDS +outpoint/GDS +outpost/SM +outpour/MJG +outpouring/M +outproduce/GSD +output/SM +outputted +outputting +outrace/GSD +outrage/GSDM +outrageous/YP +outrageousness/M +outran +outrank/GSD +outreach/SDG +outrider/MS +outrigger/SM +outright/Y +outrun/S +outrunning +outré +outscore/GDS +outsell/GS +outset/MS +outsetting +outshine/SG +outshone +outshout/GDS +outside/ZSR +outsider/PM +outsize/S +outskirt/SM +outsmart/SDG +outsold +outsource/SDJG +outspend/SG +outspent +outspoke +outspoken/YP +outspokenness/SM +outspread/SG +outstanding/Y +outstate/NX +outstation/M +outstay/SDG +outstretch/GSD +outstrip/S +outstripped +outstripping +outtake/S +outvote/GSD +outward/SYP +outwardness/M +outwear/SG +outweigh/GD +outweighs +outwit/S +outwitted +outwitting +outwore +outwork/SMDG +outworn +ouzo/SM +ova/M +oval/MYPS +ovalness/M +ovarian +ovary/SM +ovate/SDGNX +ovation/GMD +oven/MS +ovenbird/SM +over/YGS +overabundance/MS +overabundant +overachieve/SRDGZ +overact/DGVS +overage/S +overaggressive +overall/SM +overallocation +overambitious +overanxious +overarching +overarm/GSD +overate +overattentive +overawe/GDS +overbalance/DSG +overbear/GS +overbearing/YP +overbearingness/M +overbid/S +overbidding +overbite/MS +overblown +overboard +overbold +overbook/SDG +overbore +overborne +overbought +overbuild/GS +overbuilt +overburden/SDG +overburdening/Y +overbuy/GS +overcame +overcapacity/M +overcapitalize/DSG +overcareful +overcast/GS +overcasting/M +overcautious +overcerebral +overcharge/DSG +overcloud/DSG +overcoat/SMG +overcoating/M +overcome/RSG +overcomer/M +overcommitment/S +overcompensate/XGNDS +overcompensation/M +overcomplexity/M +overcomplicated +overconfidence/MS +overconfident/Y +overconscientious +overconsumption/M +overcook/SDG +overcooled +overcorrection +overcritical +overcrowd/DGS +overcurious +overdecorate/SDG +overdependent +overdetermined +overdevelop/SDG +overdid +overdo/G +overdoes +overdone +overdose/DSMG +overdraft/SM +overdraw/GS +overdrawn +overdress/GDS +overdrew +overdrive/GSM +overdriven +overdrove +overdub/S +overdubbed +overdubbing +overdue +overeager/PY +overeagerness/M +overeat/GNRS +overeater/M +overeducated +overemotional +overemphases +overemphasis/M +overemphasize/GZDSR +overenthusiastic +overestimate/DSXGN +overestimation/M +overexcite/DSG +overexercise/SDG +overexert/GDS +overexertion/SM +overexploitation +overexploited +overexpose/GDS +overexposure/SM +overextend/DSG +overextension +overfall/M +overfed +overfeed/GS +overfill/GDS +overfishing +overflew +overflight/SM +overflow/DGS +overflown +overfly/GS +overfond +overfull +overgeneralize/GDS +overgenerous +overgraze/SDG +overgrew +overground +overgrow/GSH +overgrown +overgrowth/M +overgrowths +overhand/DGS +overhang/GS +overhasty +overhaul/GRDJS +overhead/S +overhear/SRG +overheard +overhearer/M +overheat/SGD +overhung +overincredulous +overindulge/SDG +overindulgence/SM +overindulgent +overinflated +overjoy/SGD +overkill/SDMG +overladed +overladen +overlaid +overlain +overland/S +overlap/MS +overlapped +overlapping +overlarge +overlay/GS +overleaf +overlie +overload/SDG +overlong +overlook/DSG +overlord/DMSG +overloud +overly/GRS +overmanning +overmaster/GSD +overmatching +overmodest +overmuch/S +overnice +overnight/SDRGZ +overoptimism/SM +overoptimistic +overpaid +overparticular +overpass/GMSD +overpay/LSG +overpayment/M +overplay/SGD +overpopulate/DSNGX +overpopulation/M +overpopulous +overpower/GSD +overpowering/Y +overpraise/DSG +overprecise +overpressure +overprice/SDG +overprint/DGS +overproduce/SDG +overproduction/S +overprotect/GVDS +overprotection/M +overqualified +overran +overrate/DSG +overreach/DSRG +overreact/SGD +overreaction/SM +overred +overrefined +overrepresented +overridden +override/RSG +overrider/M +overripe +overrode +overrule/GDS +overrun/S +overrunning +oversample/DG +oversaturate +oversaw +oversea/S +oversee/ZRS +overseeing +overseen +overseer/M +oversell/SG +oversensitive/P +oversensitiveness/S +oversensitivity +oversexed +overshadow/GSD +overshoe/SM +overshoot/SG +overshot/S +oversight/SM +oversimple +oversimplification/M +oversimplify/GXNDS +oversize/GS +oversleep/GS +overslept +oversoft/P +oversoftness/M +oversold +overspecialization/MS +overspecialize/GSD +overspend/SG +overspent +overspill/DMSG +overspread/SG +overstaffed +overstate/SDLG +overstatement/SM +overstay/GSD +overstep/S +overstepped +overstepping +overstimulate/DSG +overstock/SGD +overstraining +overstressed +overstretch/D +overstrict +overstrike/GS +overstrung +overstuffed +oversubscribe/SDG +oversubtle +oversupply/MDSG +oversuspicious +overt/PY +overtake/RSZG +overtaken +overtax/DSG +overthrew +overthrow/GS +overthrown +overtightened +overtime/MGDS +overtire/DSG +overtone/MS +overtook +overture/DSMG +overturn/SDG +overuse/DSG +overvalue/GSD +overview/MS +overweening +overweight/GSD +overwhelm/GDS +overwhelming/Y +overwinter/SDG +overwork/GSD +overwrap +overwrite/SG +overwritten +overwrote +overwrought +overzealous/P +overzealousness/M +oviduct/SM +oviform +oviparous +ovoid/S +ovular +ovulate/GNXDS +ovulatory +ovule/MS +ovum/MS +ow/DYG +owe/S +owl/GSMDR +owlet/SM +owlish/PY +owlishness/M +own/EGDS +owned/U +owner/SM +ownership/MS +ox/MNS +oxalate/M +oxalic +oxaloacetic +oxblood/S +oxbow/SM +oxcart/MS +oxen/M +oxford/MS +oxidant/SM +oxidate/NVX +oxidation/M +oxidative/Y +oxide/SM +oxidization/MS +oxidize/JDRSGZ +oxidized/U +oxidizer/M +oxidizes/A +oxtail/M +oxyacetylene/MS +oxygen/MS +oxygenate/XSDMGN +oxygenation/M +oxyhydroxides +oxymora +oxymoron/M +oyster/GSDM +oystering/M +oz +ozone/SM +p's/A +p/XTGJ +pH/M +pa/MH +pablum/S +pabulum/SM +pace/DRSMZG +pacemaker/SM +pacer/M +pacesetter/MS +pacesetting +pachyderm/MS +pachysandra/MS +pacific +pacifically +pacification/M +pacifier/M +pacifism/MS +pacifist/MS +pacifistic +pacify/NRSDGXZ +pack/GZSJDRMB +package's +package/ARSDG +packaged/U +packager/S +packages/U +packaging/SM +packed/AU +packer/MUS +packet/MSDG +packhorse/M +packing/M +packinghouse/S +packs/UA +packsaddle/SM +pact/SM +pad/MS +padded/U +padding/SM +paddle/MZGRSD +paddler/M +paddock/SDMG +paddy/SM +padlock/SGDM +padre/MS +paean/MS +paediatrician/MS +paediatrics/M +paedophilia's +paella/SM +paeony/M +pagan/SM +paganism/MS +page/MZGDRS +pageant/SM +pageantry/SM +pageboy/SM +paged/U +pageful +pager/M +paginate/DSNGX +pagoda/MS +paid/AU +pail/SM +pailful/SM +pain/GSDM +painful/YP +painfuller +painfullest +painfulness/MS +painkiller/MS +painkilling +painless/YP +painlessness/S +painstaking/SY +paint's +paint/ADRZGS +paintbox/M +paintbrush/SM +painted/U +painter/YM +painterly/P +painting/SM +paintwork +pair/JSDMG +paired/UA +pairs/A +pairwise +paisley/MS +pajama/MDS +pal/SJMDRYTG +palace/MS +paladin/MS +palaeolithic +palaeontologists +palaeontology/M +palanquin/MS +palatability/M +palatable/P +palatableness/M +palatal/YS +palatalization/MS +palatalize/SDG +palate/BMS +palatial/Y +palatinate/SM +palatine/S +palaver/GSDM +pale/SPY +paleface/SM +paleness/S +paleographer/SM +paleography/SM +paleolithic +paleontologist/S +paleontology/MS +palette/MS +palfrey/MS +palimony/S +palimpsest/MS +palindrome/MS +palindromic +paling/M +palisade/MGSD +palish +pall/GSMD +palladium/SM +pallbearer/SM +pallet/SMGD +palletized +palliate/SDVNGX +palliation/M +palliative/SY +pallid/PY +pallidness/MS +pallor/MS +palm/GSMDR +palmate +palmer/M +palmetto/MS +palmist/MS +palmistry/MS +palmtop/S +palmy/RT +palomino/MS +palpable +palpably +palpate/SDNGX +palpation/M +palpitate/NGXSD +palpitation/M +palsy/GSDM +paltriness/SM +paltry/TRP +paludal +pampas/M +pamper/RDSG +pamperer/M +pamphlet/SM +pamphleteer/DMSG +pan/SMD +panacea/MS +panache/MS +panama/S +pancake/MGSD +panchromatic +pancreas/MS +pancreatic +panda/SM +pandemic/S +pandemonium/SM +pander/ZGRDS +pane/KMS +panegyric/SM +panel/JSGDM +paneling/M +panelist/MS +panelization +panelized +pang/GDMS +pangolin/M +panhandle/RSDGMZ +panic/SM +panicked +panicking +panicky/RT +panier's +panjandrum/M +panned +pannier/SM +panning +panoply/MSD +panorama/MS +panoramic +panpipes +pansy/SM +pant/GDS +pantaloons +pantheism/MS +pantheist/S +pantheistic +pantheon/MS +panther/SM +pantie/SM +pantiled +pantograph/M +pantomime/SDGM +pantomimic +pantomimist/SM +pantry/SM +pantsuit/SM +pantyhose +pantyliner +pantywaist/SM +pap/SZMNR +papa/MS +papacy/SM +papal/Y +paparazzi +papaw/SM +papaya/MS +paper/GJMRDZ +paperback/GDMS +paperboard/MS +paperboy/SM +paperer/M +papergirl/SM +paperhanger/SM +paperhanging/SM +paperiness/M +paperless +paperweight/MS +paperwork/SM +papery/P +papilla/M +papillae +papillary +papist/MS +papoose/SM +papped +papping +pappy/RST +paprika/MS +papyri +papyrus/M +par/ZGSJBMDR +para/MS +parable/MGSD +parabola/MS +parabolic +paraboloid/MS +paraboloidal/M +paracetamol/M +parachute/RSDMG +parachuter/M +parachutist/MS +parade/RSDMZG +parader/M +paradigm/SM +paradigmatic +paradisaic +paradisaical +paradise/MS +paradox/MS +paradoxic +paradoxical/YP +paradoxicalness/M +paraffin/GSMD +paragon/SGDM +paragraph/MRDG +paragrapher/M +paragraphs +parakeet/MS +paralegal/S +paralinguistic +parallax/SM +parallel/DSG +paralleled/U +parallelepiped/MS +parallelism/SM +parallelization/MS +parallelize/ZGDSR +parallelogram/MS +paralysis/M +paralytic/S +paralytically +paralyze/ZGDRS +paralyzed/Y +paralyzedly/S +paralyzer/M +paralyzing/Y +paralyzingly/S +paramagnet/M +paramagnetic +paramecia +paramecium/M +paramedic/MS +paramedical/S +parameter/SM +parameterization/SM +parameterize/BSDG +parameterized/U +parameterless +parametric +parametrically +parametrization +parametrize/DS +paramilitary/S +paramount/S +paramour/MS +paranoia/SM +paranoiac/S +paranoid/S +paranormal/SY +parapet/SMD +paraphernalia +paraphrase/GMSRD +paraphraser/M +paraplegia/MS +paraplegic/S +paraprofessional/SM +parapsychologist/S +parapsychology/MS +paraquat/S +parasite/SM +parasitic/S +parasitically +parasitism/SM +parasitologist/M +parasitology/M +parasol/SM +parasympathetic/S +parathion/SM +parathyroid/S +paratroop/RSZ +paratrooper/M +paratyphoid/S +parboil/DSG +parcel/SGMD +parceled/U +parceling/M +parch/GSDL +parchment/SM +pardon/ZBGRDS +pardonable/U +pardonableness/M +pardonably/U +pardoner/M +pare/S +paregoric/SM +parent/MDGJS +parentage/MS +parental/Y +parenteral +parentheses +parenthesis/M +parenthesize/GSD +parenthetic +parenthetical/Y +parenthood/MS +pares/S +paresis/M +parfait/SM +pariah/M +pariahs +parietal/S +parimutuel/S +paring/M +parish/MS +parishioner/SM +parity/ESM +park/GJZDRMS +parka/MS +parking/M +parkish +parkland/M +parklike +parkway/MS +parlance/SM +parlay/DGS +parley/MDSG +parliament/MS +parliamentarian/SM +parliamentary/U +parlor/SM +parlous +parmigiana +parochial/Y +parochialism/SM +parochiality +parodied/U +parodist/SM +parody/SDGM +parole/MSDG +parolee/MS +paroxysm/MS +paroxysmal +parquet/SMDG +parquetry/SM +parrakeet's +parred +parricidal +parricide/MS +parring +parrot/GMDS +parrotlike +parry/GSD +pars/JDSRGZ +parse +parsec/SM +parsed/U +parser/M +parsimonious/Y +parsimony/SM +parsley/MS +parsnip/MS +parson/MS +parsonage/MS +part's +part/CDGS +partake/ZGSR +partaken +partaker/M +parter/S +parterre/MS +parthenogeneses +parthenogenesis/M +partial/SY +partiality/MS +participant/MS +participate/NGVDSX +participation/M +participator/S +participatory +participial/Y +participle/MS +particle/MS +particleboard/S +particolored +particular/SY +particularistic +particularity/SM +particularization/MS +particularize/GSD +particulate/S +parting/MS +partisan/SM +partisanship/SM +partition/AMRDGS +partitioned/U +partitioner/M +partitive/S +partizan's +partly +partner/DMGS +partnership/SM +partook +partridge/MS +parturition/SM +partway +party/RSDMG +parvenu/SM +pas/S +pascal/SM +paschal/S +pasha/MS +pass/JGVBZDSR +passably +passage/MGSD +passageway/MS +passband +passbook/MS +passel/MS +passenger/MYS +passer/M +passerby +passersby +passim +passing/Y +passion/SEM +passionate/EYP +passionated +passionateness/EM +passionates +passionating +passioned +passionflower/MS +passioning +passionless +passivated +passive/SYP +passiveness/S +passivity/S +passkey/SM +passmark +passover +passport/SM +password/SDM +passé/M +past's/A +past/PGMDRS +pasta/MS +paste/MS +pasteboard/SM +pasted/UA +pastel/MS +pastern/SM +pasteup +pasteurization/MS +pasteurize/RSDGZ +pasteurized/U +pasteurizer/M +pastiche/MS +pastille/SM +pastime/SM +pastiness/SM +pastor/GSDM +pastoral/SPY +pastoralization/M +pastorate/MS +pastrami/MS +pastry/SM +pasts/A +pasturage/SM +pasture/MGSRD +pasturer/M +pasty/PTRS +pat/MNDRS +patch's +patch/EGRSD +patcher/EM +patchily +patchiness/S +patchwork/RMSZ +patchy/PRT +pate/SM +patella/MS +patellae +paten/M +patent/ZGMRDYSB +patentee/SM +pater/M +paterfamilias/SM +paternal/Y +paternalism/MS +paternalist +paternalistic +paternity/SM +paternoster/SM +path/M +pathetic +pathetically +pathfinder/MS +pathless/P +pathname/SM +pathogen/SM +pathogenesis/M +pathogenic +pathologic +pathological/Y +pathologist/MS +pathology/SM +pathos/SM +paths +pathway/MS +patience/SM +patient's/I +patient/MRYTS +patients/I +patina/SM +patine +patio/MS +patois/M +patresfamilias +patriarch/M +patriarchal +patriarchate/MS +patriarchs +patriarchy/MS +patrician/MS +patricide/MS +patrimonial +patrimony/SM +patriot/SM +patriotic/U +patriotically +patriotism/SM +patristic/S +patrol/MS +patrolled +patrolling +patrolman/M +patrolmen +patrolwoman +patrolwomen +patron/YMS +patronage/MS +patroness/S +patronization +patronize/GZRSDJ +patronized/U +patronizer/M +patronizes/A +patronizing's/U +patronizing/YM +patronymic/S +patronymically +patroon/MS +patsy/SM +patted +patten/MS +patter/RDSGJ +patterer/M +pattern/GSDM +patternless +patting +patty/SM +paucity/SM +paunch/GMSD +paunchiness/M +paunchy/RTP +pauper/SGDM +pauperism/SM +pauperize/SDG +pause/DSG +pave/GDRSJL +paved/UA +pavement/SGDM +paver/M +paves/A +pavilion/SMDG +paving's +paving/A +paw/MDSG +pawl/SM +pawn/GSDRM +pawnbroker/SM +pawnbroking/S +pawner/M +pawnshop/MS +pawpaw's +paxes +pay/AGSLB +payable/S +payback/S +paycheck/SM +payday/MS +payed +payee/SM +payer/SM +payload/SM +paymaster/SM +payment/ASM +payoff/MS +payola/MS +payout/S +payroll/MS +payslip/S +pct +pd +pea/MS +peace/GMDS +peaceable/P +peaceableness/M +peaceably +peaceful/PY +peacefuller +peacefullest +peacefulness/S +peacekeeping/S +peacemaker/MS +peacemaking/MS +peacetime/MS +peach/GSDM +peachy/RT +peacock/SGMD +peafowl/SM +peahen/MS +peak/SGDM +peaked/P +peakiness/M +peaky/P +peal/MDSG +pealed/A +peals/A +peanut/SM +pear/SYM +pearl/SGRDM +pearler/M +pearly/TRS +peartrees +peasant/SM +peasanthood +peasantry/SM +peashooter/MS +peat/SM +peats/A +peaty/TR +pebble/MGSD +pebbling/M +pebbly/TR +pecan/SM +peccadillo/M +peccadilloes +peccary/MS +peck/GZSDRM +pecker/M +pectic +pectin/SM +pectoral/S +peculate/NGDSX +peculator/S +peculiar/SY +peculiarity/MS +pecuniary +pedagogic/S +pedagogical/Y +pedagogics/M +pedagogue/SDGM +pedagogy/MS +pedal/SGRDM +pedant/SM +pedantic +pedantically +pedantry/MS +peddle/ZGRSD +peddler/M +pederast/SM +pederasty/SM +pedestal/GDMS +pedestrian/MS +pedestrianization +pedestrianize/GSD +pediatric/S +pediatrician/SM +pedicab/SM +pedicure/DSMG +pedicurist/SM +pedigree/DSM +pediment/DMS +pedlar's +pedometer/MS +pedophile/S +pedophilia +peduncle/MS +pee/ZDRS +peeing +peek/GSD +peekaboo/SM +peel/SJGZDR +peeler/M +peeling/M +peen/GSDM +peep/SGZDR +peeper/M +peephole/SM +peepshow/MS +peepy +peer/DMG +peerage/MS +peeress/MS +peerless/PY +peerlessness/M +peeve/GZMDS +peevers/M +peevish/YP +peevishness/SM +peewee/S +peg/MS +pegboard/SM +pegged +pegging +peignoir/SM +pejoration/SM +pejorative/SY +peke/MS +pekingese +pekoe/SM +pelagic +pelf/SM +pelican/SM +pellagra/SM +pellet/SGMD +pellucid +pelt/GSDR +pelter/M +pelvic/S +pelvis/SM +pemmican/SM +pen/M +penal/Y +penalization/SM +penalize/SDG +penalized/U +penalty/MS +penance/SDMG +pence/M +penchant/MS +pencil/SGJMD +pend/DCGS +pendant/SM +pendent/CS +pendulous +pendulum/MS +penetrability/SM +penetrable +penetrate/SDVGNX +penetrating/Y +penetration/M +penetrative/PY +penetrativeness/M +penetrator/MS +penguin/MS +penicillin/SM +penile +peninsula/SM +peninsular +penis/MS +penitence/MS +penitent/SY +penitential/YS +penitentiary/MS +penknife/M +penknives +penlight/MS +penman/M +penmanship/MS +penmen +pennant/SM +penned +penniless +penning +pennis +pennon/SM +penny/SM +pennyweight/SM +pennyworth/M +penologist/MS +penology/MS +pens/V +pension/ZGMRDBS +pensioner/M +pensive/PY +pensiveness/S +pent/AS +pentacle/MS +pentagon/SM +pentagonal/SY +pentagram/MS +pentameter/SM +pentathlete/S +pentathlon/MS +pentatonic +pentecostal +penthouse/SDGM +penuche/SM +penultimate/SY +penumbra/MS +penumbrae +penurious/YP +penuriousness/MS +penury/SM +peon/MS +peonage/MS +peony/SM +people/SDMG +pep/SM +pepped +pepper/SGRDM +peppercorn/MS +pepperer/M +peppergrass/M +peppermint/MS +pepperoni/S +peppery +peppiness/SM +pepping +peppy/PRT +pepsin/SM +peptic/S +peptidase/SM +peptide/SM +peptizing +per/K +peradventure/S +perambulate/DSNGX +perambulation/M +perambulator/MS +percale/MS +perceivably +perceive/DRSZGB +perceived/U +perceiver/M +percent/MS +percentage/MS +percentile/SM +percept/VMS +perceptible +perceptibly +perception/MS +perceptional +perceptive/YP +perceptiveness/MS +perceptual/Y +perch/GSDM +perchance +perchlorate/M +perchlorination +percipience/MS +percipient/S +percolate/NGSDX +percolation/M +percolator/MS +percuss/DSGV +percussion/SAM +percussionist/MS +percussive/PY +percussiveness/M +percutaneous/Y +perdition/MS +perdurable +peregrinate/XSDNG +peregrination/M +peregrine/S +peremptorily +peremptory/P +perennial/SY +perestroika/S +perfect/DRYSTGVP +perfecta/S +perfecter/M +perfectibility/MS +perfectible +perfection/MS +perfectionism/MS +perfectionist/MS +perfective/PY +perfectiveness/M +perfectness/MS +perfidious/YP +perfidiousness/M +perfidy/MS +perforate/XSDGN +perforated/U +perforation/M +perforce +perform/SDRZGB +performance/MS +performed/U +performer/M +perfume/ZMGSRD +perfumer/M +perfumery/SM +perfunctorily +perfunctoriness/M +perfunctory/P +perfused +perfusion/M +pergola/SM +perhaps/S +pericardia +pericardium/M +perigee/SM +perihelia +perihelion/M +peril/GSDM +perilous/PY +perilousness/M +perimeter/MS +perinatal +perinea +perineum/M +period/MS +periodic +periodical/YMS +periodicity/MS +periodontal/Y +periodontics/M +periodontist/S +peripatetic/S +peripheral/SY +periphery/SM +periphrases +periphrasis/M +periphrastic +periscope/SDMG +perish/BZGSRD +perishable/SM +perishing/Y +peristalses +peristalsis/M +peristaltic +peristyle/MS +peritoneal +peritoneum/SM +peritonitis/MS +periwig/MS +periwigged +periwigging +periwinkle/SM +perjure/SRDZG +perjurer/M +perjury/MS +perk/GDS +perkily +perkiness/S +perky/TRP +perm/MDGS +permafrost/MS +permalloy/M +permanence/SM +permanency/MS +permanent/YSP +permanentness/M +permeability/SM +permeable/P +permeableness/M +permeate/NGVDSX +permissibility/M +permissible/P +permissibleness/M +permissibly +permission/SM +permissive/YP +permissiveness/MS +permit/SM +permitted +permitting +permutation/MS +permute/SDG +pernicious/PY +perniciousness/MS +peroration/SM +peroxidase/M +peroxide/MGDS +perpend/DG +perpendicular/SY +perpendicularity/SM +perpetrate/NGXSD +perpetration/M +perpetrator/SM +perpetual/SY +perpetuate/NGSDX +perpetuation/M +perpetuity/MS +perplex/DSG +perplexed/Y +perplexity/MS +perquisite/SM +persecute/XVNGSD +persecution/M +persecutor/MS +persecutory +perseverance/MS +persevere/GSD +persevering/Y +persiflage/MS +persimmon/SM +persist/DRSG +persistence/SM +persistent/Y +persnickety +person's/U +person/BMS +persona/M +personable/P +personableness/M +personae +personage/SM +personal/YS +personality/SM +personalization/CMS +personalize/CSDG +personalized/U +personalty/MS +personification/M +personifier/M +personify/XNGDRS +personnel/SM +persons/U +perspective/YMS +perspex +perspicacious/PY +perspicaciousness/M +perspicacity/S +perspicuity/SM +perspicuous/YP +perspicuousness/M +perspiration/MS +perspire/DSG +persuade/ZGDRSB +persuaded/U +persuader/M +persuasion/SM +persuasive/U +persuasively +persuasiveness/MS +pert/YRTSP +pertain/GSD +pertinacious/YP +pertinaciousness/M +pertinacity/MS +pertinence/S +pertinent/YS +pertness/MS +perturb/GDS +perturbation/MS +perturbed/U +pertussis/SM +peruke/SM +perusal/SM +peruse/RSDZG +peruser/M +pervade/SDG +pervasion/M +pervasive/PY +pervasiveness/MS +perverse/PXYNV +perverseness/SM +perversion/M +perversity/MS +pervert/DRSG +perverted/YP +perverter/M +perviousness +peseta/SM +peskily +peskiness/S +pesky/RTP +peso/MS +pessimal/Y +pessimism/SM +pessimist/SM +pessimistic +pessimistically +pest/RZSM +pester/DG +pesticide/MS +pestiferous +pestilence/SM +pestilent/Y +pestilential/Y +pestle/SDMG +pesto/S +pet/SMRZ +petal/SDM +petard/MS +petcock/SM +peter/GD +pethidine/M +petiole/SM +petite/XNPS +petiteness/M +petition's/A +petition/GZMRD +petitioner/M +petitions/A +petits +petrel/SM +petri +petrifaction/SM +petrify/NDSG +petrochemical/SM +petrodollar/MS +petroglyph/M +petrol/MS +petrolatum/MS +petroleum/MS +petrolled +petrolling +petrologist/MS +petrology/MS +petted +petter/MS +petticoat/SMD +pettifog/S +pettifogged +pettifogger/SM +pettifogging +pettily +pettiness/S +petting +pettis +pettish/YP +pettishness/M +petty/PRST +petulance/MS +petulant/Y +petunia/SM +pew/SM +pewee/MS +pewit/MS +pewter/SRM +peyote/SM +pf +pfennig/SM +pg +phaeton/MS +phage/M +phagocyte/SM +phalanger/MS +phalanges +phalanx/SM +phalli +phallic +phallus/M +phantasm/SM +phantasmagoria/SM +phantasmal +phantasy's +phantom/MS +pharaoh +pharaohs +pharisaic +pharisee/S +pharmaceutic/S +pharmaceutical/SY +pharmaceutics/M +pharmacist/SM +pharmacological/Y +pharmacologist/SM +pharmacology/SM +pharmacopoeia/SM +pharmacy/SM +pharyngeal/S +pharynges +pharyngitides +pharyngitis/M +pharynx/M +phase/DSRGZM +phaseout/S +pheasant/SM +phenacetin/MS +phenobarbital/SM +phenol/MS +phenolic +phenolphthalein/M +phenomena/SM +phenomenal/Y +phenomenological/Y +phenomenology/MS +phenomenon/SM +phenotype/MS +phenyl/M +phenylalanine/M +pheromone/MS +phew/S +phi/SM +phial/MS +phialled +phialling +philander/SRDGZ +philanderer/M +philanthropic +philanthropically +philanthropist/MS +philanthropy/SM +philatelic +philatelist/MS +philately/SM +philharmonic/S +philippic/SM +philistine/S +philistinism/S +philodendron/MS +philological/Y +philologist/MS +philology/MS +philosopher/MS +philosophic +philosophical/Y +philosophize/ZDRSG +philosophized/U +philosophizer/M +philosophizes/U +philosophy/MS +philter/SGDM +philtre/DSMG +phlebitides +phlebitis/M +phlegm/SM +phlegmatic +phlegmatically +phloem/MS +phlox/M +phobia/SM +phobic/S +phoebe/SM +phoenix/MS +phone/DSGM +phoneme/SM +phonemic/S +phonemically +phonemics/M +phonetic/S +phonetically +phonetician/SM +phonetics/M +phonic/S +phonically +phonics/M +phoniness/MS +phonograph/RM +phonographer/M +phonographic +phonographs +phonologic +phonological/Y +phonologist/MS +phonology/MS +phonon/M +phony/PTRSDG +phooey/S +phosphatase/M +phosphate/MS +phosphide/M +phosphine/MS +phosphor/MS +phosphoresce +phosphorescence/SM +phosphorescent/Y +phosphoric +phosphorous +phosphorus/SM +photo/SGMD +photocell/MS +photochemical/Y +photochemistry/M +photocopier/M +photocopy/MRSDZG +photoelectric +photoelectrically +photoelectronic +photoelectrons +photoengrave/RSDJZG +photoengraver/M +photoengraving/M +photofinishing/MS +photogenic +photogenically +photograph's +photograph/AGD +photographer/SM +photographic +photographically +photographs/A +photography/MS +photojournalism/SM +photojournalist/SM +photoluminescence/M +photolysis/M +photolytic +photometer/SM +photometric +photometrically +photometry/M +photomicrograph/M +photomicrography/M +photomultiplier/M +photon/MS +photorealism +photosensitive +photosphere/M +photostatic +photosyntheses +photosynthesis/M +photosynthesize/DSG +photosynthetic +phototypesetter +phototypesetting/M +phrasal +phrase's +phrase/AGDS +phrasebook +phrasemaking +phraseology/MS +phrasing/SM +phrenological/Y +phrenologist/MS +phrenology/MS +phyla/M +phylactery/MS +phylae +phylogeny/MS +phylum/M +phys +physic/SM +physical/PYS +physicality/M +physician/SM +physicist/MS +physicked +physicking +physiochemical +physiognomy/SM +physiography/MS +physiologic +physiological/Y +physiologist/SM +physiology/MS +physiotherapist/MS +physiotherapy/SM +physique/MSD +phytoplankton/M +pi/ZGDRH +pianism/M +pianissimo/S +pianist/SM +pianistic +piano/SM +pianoforte/MS +pianola +piaster/MS +piazza/SM +pibroch/M +pibrochs +pica/SM +picador/MS +picaresque/S +picayune/S +piccalilli/MS +piccolo/MS +pick/GZSJDR +pickaback's +pickax/GMSD +pickaxe's +picker/MG +pickerel/MS +picket/MSRDZG +picketer/M +pickle/SDMG +pickoff/S +pickpocket/GSM +pickup/SM +picky/RT +picnic/SM +picnicked +picnicker/MS +picnicking +picofarad/MS +picojoule +picoseconds +picot/DMGS +pictograph/M +pictographs +pictorial/PYS +pictorialness/M +picture/MGSD +picturesque/PY +picturesqueness/SM +piddle/GSD +piddly +pidgin/SM +pie/MS +piebald/S +piece/GMDSR +piecemeal +piecer/M +piecewise +piecework/ZSMR +pieceworker/M +piedmont +pieing +pier/M +pierce/RSDZGJ +piercer/M +piercing/Y +piety/SM +piezoelectric +piezoelectricity/M +piffle/MGSD +pig/MLS +pigeon/DMGS +pigeonhole/SDGM +pigged +piggery/M +pigging +piggish/YP +piggishness/SM +piggy/RSMT +piggyback/MSDG +pigheaded/YP +pigheadedness/S +piglet/MS +pigment/MDSG +pigmentation/MS +pigpen/SM +pigroot +pigskin/MS +pigsty/SM +pigswill/M +pigtail/SMD +pike/MZGDRS +piker/M +pikestaff/MS +pilaf/MS +pilaster/SM +pilau's +pilchard/SM +pile/JDSMZG +pileup/MS +pilfer/ZGSRD +pilferage/SM +pilferer/M +pilgrim/MS +pilgrimage/DSGM +piling/M +pill/GSMD +pillage/RSDZG +pillar/DMSG +pillbox/MS +pillion/DMGS +pillory/MSDG +pillow/GDMS +pillowcase/SM +pillowslip/S +pilot/DMGS +pilothouse/SM +piloting/M +pimento/MS +pimiento/SM +pimp/GSMYD +pimpernel/SM +pimple/SDM +pimplike +pimply/TRM +pin's +pin/US +pinafore/MS +pinball/MS +pincer/GSD +pinch/GRSD +pincher/M +pincushion/SM +pine/MNGXDS +pineapple/MS +pined/A +pines/A +pinfeather/SM +ping/GDRM +pinhead/SMD +pinheaded/P +pinhole/SM +pining/A +pinion/DMG +pink/GTYDRMPS +pinkeye/MS +pinkie/SM +pinkish/P +pinkness/S +pinko/MS +pinky's +pinnacle/MGSD +pinnate +pinned/U +pinning/S +pinochle/SM +pinpoint/SDG +pinprick/MDSG +pinsetter/SM +pinstripe/SDM +pint/MRS +pintail/SM +pinto/S +pinup/MS +pinwheel/DMGS +piny/RT +pinyin +pion/M +pioneer/SDMG +pious/YP +piousness/MS +pip/JSZMGDR +pipe/MS +pipeline/DSMG +piper/M +pipet's +pipette/MGSD +pipework +piping/YM +pipit/MS +pipped +pippin/SM +pipping +pipsqueak/SM +piquancy/MS +piquant/PY +piquantness/M +pique/GMDS +piracy/MS +piranha/SM +pirate/MGSD +piratical/Y +pirogi +pirogies +pirouette/MGSD +pis +piscatorial +pismire/SM +piss/DSRG! +pistachio/MS +piste/SM +pistil/MS +pistillate +pistol/SMGD +pistole/M +pistoleers +piston/SM +pit/MS +pita/SM +pitapat/S +pitapatted +pitapatting +pitch/RSDZG +pitchblende/SM +pitcher/M +pitchfork/GDMS +pitching/M +pitchman/M +pitchmen +pitchstone/M +piteous/YP +piteousness/SM +pitfall/SM +pith/MGDS +pithily +pithiness/SM +piths +pithy/RTP +pitiable/P +pitiableness/M +pitiably +pitier/M +pitiful/PY +pitifuller +pitifullest +pitifulness/M +pitiless/PY +pitilessness/SM +pitman/M +piton/SM +pittance/SM +pitted +pitting +pituitary/SM +pity/ZDSRMG +pitying/Y +pivot/DMSG +pivotal/Y +pivoting/M +pix/DSG +pixel/SM +pixie/MS +pixiness +pixmap/SM +pizazz/S +pizza/SM +pizzeria/SM +pizzicati +pizzicato +piñata/S +piñon/S +pj's +pk +pkg +pkt +pkwy +pl +placard/DSMG +placate/NGVXDRS +placatory +place/DSRJLGZM +placeable/A +placebo/SM +placed/EAU +placeholder/S +placekick/DGS +placeless/Y +placement/AMES +placenta/SM +placental/S +placer/EM +places/EA +placid/PY +placidity/SM +placidness/M +placing/AE +placket/SM +plagiarism/MS +plagiarist/MS +plagiarize/GZDSR +plagiary/SM +plague/MGRSD +plagued/U +plaguer/M +plaice/M +plaid/DMSG +plain/SPTGRDY +plainclothes +plainclothesman +plainclothesmen +plainness/MS +plainsman/M +plainsmen +plainsong/SM +plainspoken +plaint/VMS +plaintiff/MS +plaintive/YP +plaintiveness/M +plait/SRDMG +plaiting/M +plan/DRMSGZ +planar +planarity +plane's +plane/SCGD +planeload +planer/M +planet/MS +planetarium/MS +planetary +planetesimal/M +planetoid/SM +plangency/S +plangent +plank/SJMDG +planking/M +plankton/MS +planned/U +planner/SM +planning +planoconcave +planoconvex +plant's +plant/SADG +plantain/MS +plantar +plantation/MS +planter/MS +planting/S +plantlike +plaque/MS +plash/GSDM +plasm/M +plasma/MS +plasmid/S +plaster/MDRSZG +plasterboard/MS +plasterer/M +plastering/M +plasterwork/M +plastic/MYS +plastically +plasticine +plasticity/SM +plasticize/GDS +plat/JDNRSGXZ +plate/SM +plateau/GDMS +plateful/S +platelet/SM +platen/M +plater/M +platform/SGDM +plating/M +platinize/GSD +platinum/MS +platitude/SM +platitudinous/Y +platonic +platoon/MDSG +platted +platter/MS +platting +platy/TR +platypus/MS +platys +plaudit/MS +plausibility/S +plausible/P +plausibly +play/DRSEBG +playability/U +playable/U +playact/SJDG +playacting/M +playback/MS +playbill/SM +playboy/SM +played/A +player's/E +player/SM +playfellow/S +playful/PY +playfulness/MS +playgirl/SM +playgoer/MS +playground/MS +playgroup/S +playhouse/SM +playing/S +playmate/MS +playoff/S +playpen/SM +playroom/SM +plays/A +plaything/MS +playtime/SM +playwright/SM +playwriting/M +plaza/SM +plea/SM +plead/ZGJRDS +pleader/MA +pleading/MY +pleas/RSDJG +pleasant/UYP +pleasanter +pleasantest +pleasantness/SMU +pleasantry/MS +please/Y +pleased/EU +pleaser/M +pleases/E +pleasing/YP +pleasingness/M +pleasurable/P +pleasurableness/M +pleasurably +pleasure's/E +pleasure/MGBDS +pleasureful +pleasures/E +pleat/RDMGS +pleater/M +plebe/MS +plebeian/SY +plebiscite/SM +plectra +plectrum/SM +pledge/RSDMG +pledger/M +plenary/S +plenipotentiary/S +plenitude/MS +plenteous/PY +plenteousness/M +plentiful/YP +plentifulness/M +plenty/SM +plenum/M +pleonasm/MS +plethora/SM +pleura/M +pleurae +pleural +pleurisy/SM +plexus/SM +pliability/MS +pliable/P +pliableness/M +pliancy/MS +pliant/YP +pliantness/M +plication/MA +plier/MA +plight/GMDRS +plimsolls +plink/GRDS +plinker/M +plinth/M +plinths +plod/S +plodded +plodder/SM +plodding/SY +plop/SM +plopped +plopping +plosive +plot/SM +plotted/A +plotter/MDSG +plotting +plover/MS +plow/SGZDRM +plowed/U +plower/M +plowman/M +plowmen +plowshare/MS +ploy's +ploy/SCDG +pluck/SGRD +plucker/M +pluckily +pluckiness/SM +plucky/TPR +plug's +plug/US +pluggable +plugged/UA +plugging/AU +plughole +plum/SMDG +plumage/DSM +plumb/JSZGMRD +plumbago/M +plumbed/U +plumber/M +plumbing/M +plume/SM +plummer +plummest +plummet/DSG +plummy +plump/RDNYSTGP +plumper/M +plumpness/S +plumy/TR +plunder/GDRSZ +plunge/RSDZG +plunger/M +plunk/ZGSRD +plunker/M +pluperfect/S +plural/SY +pluralism/MS +pluralist/S +pluralistic +plurality/SM +pluralization/MS +pluralize/GZRSD +pluralizer/M +plus/S +plush/RSYMTP +plushness/MS +plushy/RPT +plussed +plussing +plutocracy/MS +plutocrat/SM +plutocratic +plutonium/SM +pluvial/S +ply/AZNGRSD +plywood/MS +pm +pneumatic/S +pneumatically +pneumatics/M +pneumonia/MS +poach/ZGSRD +poacher/M +pock/GDMS +pocket/MSRDG +pocketbook/SM +pocketful/SM +pocketing/M +pocketknife/M +pocketknives +pockmark/MDSG +pod/SM +podded +podding +podge/ZR +podiatrist/MS +podiatry/MS +podium/MS +poem/MS +poesy/GSDM +poet/MS +poetaster/MS +poetess/MS +poetic/S +poetical/U +poetically +poeticalness +poetics/M +poetry/SM +pogo +pogrom/GMDS +poi/SM +poignancy/MS +poignant/Y +poinciana/SM +poinsettia/SM +point/RDMZGS +pointblank +pointed/PY +pointedness/M +pointer/M +pointillism/SM +pointillist/SM +pointing/M +pointless/YP +pointlessness/SM +pointy/TR +pois/GDS +poise/M +poison/RDMZGSJ +poisoner/M +poisoning/M +poisonous/PY +poke/DRSZG +poker/M +pokerface/D +poky/SRT +pol/GMDRS +polar/S +polarimeter/SM +polarimetry +polariscope/M +polarity/MS +polarization/CMS +polarize/RSDZG +polarized/UC +polarizes/C +polarizing/C +polarogram/SM +polarograph +polarography/M +pole/MS +polecat/SM +polemic/S +polemical/Y +polemicist/S +polemics/M +poler/M +polestar/S +poleward/S +police/MSDG +policeman/M +policemen/M +policewoman/M +policewomen +policy/SM +policyholder/MS +policymaker/S +policymaking +polio/SM +poliomyelitides +poliomyelitis/M +polis/M +polish/RSDZGJ +polished/U +polisher/M +politburo/S +polite/PRTY +politeness/MS +politesse/SM +politic/S +political/U +politically +politician/MS +politicization/S +politicize/CSDG +politicked +politicking/SM +politico/SM +politics/M +polity/MS +polka/SDMG +poll/MDNRSGX +pollack/SM +polled/U +pollen/GDM +pollinate/XSDGN +pollination/M +pollinator/MS +polliwog/SM +pollock's +pollster/MS +pollutant/MS +pollute/RSDXZVNG +polluted/U +polluter/M +pollution/M +pollywog's +polo/MS +polonaise/MS +polonium/MS +poltergeist/SM +poltroon/MS +polyandrous +polyandry/MS +polyatomic +polybutene/MS +polycarbonate +polychemicals +polychrome +polyclinic/MS +polycrystalline +polyelectrolytes +polyester/SM +polyether/S +polyethylene/SM +polygamist/MS +polygamous/Y +polygamy/MS +polyglot/S +polygon/MS +polygonal/Y +polygraph/MDG +polygraphs +polygynous +polyhedral +polyhedron/MS +polyisobutylene +polyisocyanates +polymath/M +polymaths +polymer/MS +polymerase/S +polymeric +polymerization/SM +polymerize/SDG +polymorph/M +polymorphic +polymorphism/MS +polymyositis +polynomial/YMS +polyp/MS +polyphonic +polyphony/MS +polyphosphate/S +polypropylene/MS +polystyrene/SM +polysyllabic +polysyllable/SM +polytechnic/MS +polytheism/SM +polytheist/SM +polytheistic +polythene/M +polytonal/Y +polytopes +polyunsaturated +polyurethane/SM +polyvinyl/MS +pomade/MGSD +pomander/MS +pomegranate/SM +pommel/GSMD +pomp/SM +pompadour/MDS +pompano/SM +pompom/SM +pompon's +pomposity/MS +pompous/YP +pompousness/S +ponce/M +poncho/MS +pond/SMDRGZ +ponder/ZGRD +ponderer/M +ponderous/PY +ponderousness/MS +pone/SM +pongee/MS +poniard/GSDM +pons/M +pontiff/MS +pontifical/YS +pontificate/XGNDS +pontoon/SMDG +pony/DSMG +ponytail/SM +pooch/GSDM +poodle/MS +poof/MS +pooh/DG +poohs +pool/MDSG +poolroom/MS +poolside +poop/MDSG +poor/TYRP +poorboy +poorhouse/MS +poorness/MS +pop/SM +popcorn/MS +pope/SM +popgun/SM +popinjay/MS +poplar/SM +poplin/MS +popover/SM +poppa/MS +popped +popper/SM +poppet/M +popping +poppy/SDM +poppycock/MS +poppyseed +populace/MS +popular/YS +popularism +popularity/UMS +popularization/SM +popularize/A +popularized +popularizer/MS +popularizes/U +popularizing +populate/CXNGDS +populated/UA +populates/A +populating/A +population/MC +populism/S +populist/SM +populous/YP +populousness/MS +porcelain/SM +porch/SM +porcine +porcupine/MS +pore/ZGDRS +porgy/SM +poring/Y +pork/ZRMS +porker/M +porky/TSR +porn/S +porno/S +pornographer/SM +pornographic +pornographically +pornography/SM +porosity/SM +porous/PY +porousness/MS +porphyritic +porphyry/MS +porpoise/DSGM +porridge/MS +porringer/MS +port/ABSGZMRD +portability/S +portable/U +portables +portably +portage/ASM +portaged +portaging +portal/SM +portamento/M +portcullis/MS +ported/CE +portend/SDG +portent/SM +portentous/PY +portentousness/M +porter's/A +porter/DMG +porterage/M +porterhouse/SM +portfolio/MS +porthole/SM +portico/M +porticoes +porting/E +portion/KGSMD +portière/SM +portliness/SM +portly/PTR +portmanteau/SM +portrait/MS +portraitist/SM +portraiture/MS +portray/GDRS +portrayal/SM +portrayer/M +ports/CE +portulaca/MS +pose/ZGKDRSE +posed/CA +poser/KME +poses/CA +poseur/MS +posh/DSRGT +posing/CA +posit/SCGD +positifs +position's/EC +position/KGASMD +positionable +positional/KY +positions/EC +positive/RSPYT +positiveness/S +positivism/M +positivist/S +positivity +positron/SM +poss/S +posse/M +possess/AGEDS +possessed/PY +possession/AEMS +possessional +possessive/PSMY +possessiveness/MS +possessor/MS +possibility/SM +possible/TRS +possibly +possum/MS +post's +post/ASDRJG +postage/MS +postal/S +postbag/M +postbox/SM +postcard/SM +postcode/SM +postcondition/S +postconsonantal +postdate/DSG +postdoctoral +poster/MS +posterior/SY +posteriori +posterity/SM +postfix/GDS +postgraduate/SM +posthaste/S +posthumous/YP +posthumousness/M +posthypnotic +postilion/MS +postindustrial +posting/M +postlude/MS +postman/M +postmarital +postmark/GSMD +postmaster/SM +postmen +postmeridian +postmistress/MS +postmodern +postmodernist +postmortem/S +postnasal +postnatal +postoperative/Y +postorder +postpaid +postpartum +postpone/GLDRS +postponement/S +postpositions +postprandial +postscript/SM +postsecondary +postulate/XGNSD +postulation/M +postural +posture/MGSRD +posturer/M +postvocalic +postwar +posy/SM +pot/CMS +potability/SM +potable/SP +potableness/M +potage/M +potash/MS +potassium/MS +potato/M +potatoes +potbelly/MSD +potboil/ZR +potboiler/M +potency/MS +potent/YS +potentate/SM +potential/SY +potentiality/MS +potentiating +potentiometer/SM +potful/SM +pothead/MS +pother/GDMS +potherb/MS +potholder/MS +pothole/SDMG +potholing/M +pothook/SM +potion/SM +potlatch/SM +potluck/MS +potpie/SM +potpourri/SM +potsherd/MS +potshot/S +pottage/SM +potted +potter/RDMSG +pottery/MS +potting +potty/SRT +pouch/SDMG +poulterer/MS +poultice/DSMG +poultry/MS +pounce/SDG +pound/KRDGS +poundage/MS +pounder/MS +pour/DSG +pourer's +pout/GZDRS +pouter/M +poverty/MS +pow/RZ +powder/RDGMS +powderpuff +powdery +power/GMD +powerboat/MS +powerful/YP +powerfulness/M +powerhouse/MS +powerless/YP +powerlessness/SM +powwow/GDMS +pox/GMDS +pp +ppm +ppr +pr +practicability/S +practicable/P +practicably +practical/YPS +practicality/SM +practicalness/M +practice/BDRSMG +practiced/U +practicer/M +practicum/SM +practitioner/SM +praetor/MS +praetorian/S +pragmatic/S +pragmatical/Y +pragmatics/M +pragmatism/MS +pragmatist/MS +prairie/MS +praise's +praise/ESDG +praiser/S +praiseworthiness/MS +praiseworthy/P +praising/Y +praline/MS +pram/MS +prance/ZGSRD +prancer/M +prancing/Y +prank/SMDG +prankster/SM +praseodymium/SM +prate/DSRGZ +prater/M +pratfall/MS +prating/Y +prattle/DRSGZ +prattler/M +prattling/Y +prawn/MDSG +praxes +praxis/M +pray/DRGZS +prayer/M +prayerbook +prayerful/YP +prayerfulness/M +preach/DRSGLZJ +preacher/M +preaching/Y +preachment/MS +preachy/RT +preadolescence/S +preallocate/XGNDS +preallocation/M +preallocator/S +preamble/MGDS +preamp +preamplifier/M +prearrange/LSDG +prearrangement/SM +preassign/SDG +preauthorize +prebendary/M +precancel/DGS +precancerous +precarious/PY +precariousness/MS +precaution/SGDM +precautionary +precede/DSG +precedence/SM +precedent/SDM +precedented/U +precept/SMV +preceptive/Y +preceptor/MS +precess/DSG +precession/M +precinct/MS +preciosity/MS +precious/PYS +preciousness/S +precipice/MS +precipitable +precipitant/S +precipitate/YNGVPDSX +precipitateness/M +precipitation/M +precipitous/YP +precipitousness/M +precise/XYTRSPN +preciseness/SM +precision/M +preclude/GDS +preclusion/S +precocious/YP +precociousness/MS +precocity/SM +precode/D +precognition/SM +precognitive +precollege/M +precolonial +precomputed +preconceive/GSD +preconception/SM +precondition/GMDS +preconscious +precook/GDS +precursor/SM +precursory +precut +predate/NGDSX +predation/CMS +predator/SM +predatory +predecease/SDG +predecessor/MS +predeclared +predecline +predefine/GSD +predefinition/SM +predesignate/GDS +predestination/SM +predestine/SDG +predetermination/MS +predetermine/ZGSRD +predeterminer/M +predicable/S +predicament/SM +predicate/VGNXSD +predication/M +predicator +predict/BSDGV +predictability/UMS +predictable/U +predictably/U +predicted/U +prediction/MS +predictive/Y +predictor/MS +predigest/GDS +predilect +predilection/SM +predispose/SDG +predisposition/MS +predoctoral +predominance/SM +predominant/Y +predominate/YSDGN +predomination/M +preemie/MS +preeminence/SM +preeminent/Y +preemployment/M +preempt/GVSD +preemption/SM +preemptive/Y +preemptor/M +preen/SRDG +preener/M +preexist/DSG +preexistence/SM +preexistent +pref/RZ +prefab/MS +prefabbed +prefabbing +prefabricate/XNGDS +prefabrication/M +preface/DRSGM +prefacer/M +prefatory +prefect/MS +prefecture/MS +prefer/BL +preferable/P +preferableness/M +preferably +preference/MS +preferential/Y +preferment/SM +preferred +preferring +prefiguration/M +prefigure/SDG +prefix/MDSG +preflight/SGDM +preform/DSG +pregnancy/SM +pregnant/Y +preheat/GDS +prehensile +prehistoric +prehistorical/Y +prehistory/SM +preindustrial +preinitialize/SDG +preinterview/M +preisolated +prejudge/DRSG +prejudger/M +prejudgment/SM +prejudice/MSDG +prejudiced/U +prejudicial/PY +prekindergarten/MS +prelacy/MS +prelate/SM +preliminarily +preliminary/S +preliterate/S +preloaded +prelude/GMDRS +preluder/M +premarital/Y +premarket +premature/SPY +prematureness/M +prematurity/M +premed/S +premedical +premeditate/XDSGNV +premeditated/Y +premeditation/M +premenstrual +premier/GSDM +premiere/MS +premiership/SM +premise/GMDS +premiss's +premium/MS +premix/GDS +premolar/S +premonition/SM +premonitory +prenatal/Y +prenuptial +preoccupation/MS +preoccupy/DSG +preoperative +preordain/DSLG +prep/SM +prepackage/GSD +prepaid +preparation/SM +preparative/SYM +preparatory +prepare/ZDRSG +prepared/UP +preparedly +preparedness/USM +prepay/GLS +prepayment/SM +prepender/S +prepends +preplanned +preponderance/SM +preponderant/Y +preponderate/DSYGN +preposition/SDMG +prepositional/Y +prepossess/GSD +prepossessing/U +prepossession/MS +preposterous/PY +preposterousness/M +prepped +prepping +preppy/RST +preprepared +preprint/SGDM +preprocessed +preprocessing +preprocessor/S +preproduction +preprogrammed +prepubescence/S +prepubescent/S +prepublication/M +prepuce/SM +prequel/S +preradiation +prerecord/DGS +preregister/DSG +preregistration/MS +prerequisite/SM +prerogative/SDM +pres/S +presage/GMDRS +presager/M +presbyopia/MS +presbyter/MS +presbyterian +presbytery/MS +preschool/RSZ +prescience/SM +prescient/Y +prescribe/RSDG +prescribed/U +prescriber/M +prescript/SVM +prescription/SM +prescriptive/Y +preselect/SGD +presence/SM +present/SLBDRYZGP +presentable/P +presentableness/M +presentably/A +presentation/AMS +presentational/A +presented/A +presenter/A +presentiment/MS +presentment/SM +presents/A +preservation/SM +preservationist/S +preservative/SM +preserve/DRSBZG +preserved/U +preserver/M +preset/S +presetting +preshrank +preshrink/SG +preshrunk +preside/DRSG +presidency/MS +president/SM +presidential/Y +presider/M +presidia +presidium/M +presoaks +presort/GDS +press/ACDSG +pressed/U +presser/MS +pressing/YS +pressingly/C +pressman/M +pressmen +pressure/DSMG +pressurization/MS +pressurize/DSRGZ +pressurized/U +prestidigitate/NX +prestidigitation/M +prestidigitator/M +prestidigitatorial +prestige/MS +prestigious/PY +presto/S +presumably +presume/BGDRS +presumer/M +presuming/Y +presumption/MS +presumptive/Y +presumptuous/YP +presumptuousness/SM +presuppose/GDS +presupposition/S +pretax +preteen/S +pretend/SDRZG +pretended/Y +pretender/M +pretending/U +pretense/MNVSX +pretension/GDM +pretentious/UYP +pretentiousness/S +preterit/SM +preterite's +preternatural/Y +pretest/SDG +pretext/SMDG +pretreated +pretreatment/S +pretrial +prettify/SDG +prettily +prettiness/SM +pretty/TGPDRS +pretzel/SM +prevail/SGD +prevailing/Y +prevalence/MS +prevalent/SY +prevaricate/DSXNG +prevaricator/MS +prevent/BSDRGV +preventable/U +preventably +preventative/S +preventer/M +prevention/MS +preventive/SPY +preventiveness/M +preview/ZGSDRM +previous/Y +prevision/SGMD +prewar +prexes +prey/SMDG +preyer's +priapic +price's +price/AGSD +priced/U +priceless +pricer/MS +pricey +pricier +priciest +prick/RDSYZG +pricker/M +pricking/M +prickle/GMDS +prickliness/S +prickly/RTP +pride/GMDS +prideful/Y +prier/M +priest/SMYDG +priestess/MS +priesthood/SM +priestliness/SM +priestly/PTR +prig/SM +prigged +prigging +priggish/PYM +priggishness/S +prim/SPJGZYDR +primacy/MS +primal +primarily +primary/MS +primate/MS +prime/PYS +primed/U +primely/M +primeness/M +primer/M +primeval/Y +priming/M +primitive/YPS +primitiveness/SM +primitivism/M +primmed +primmer +primmest +primming +primness/MS +primogenitor/MS +primogeniture/MS +primordial/YS +primp/DGS +primrose/MGSD +prince/SMY +princedom/MS +princeliness/SM +princely/PRT +princess/MS +principal/SY +principality/MS +principle/SDMG +principled/U +print/AGDRS +printable/U +printably +printed/U +printer/AM +printers +printing/SM +printmake/ZGR +printmaker/M +printmaking/M +printout/S +prior/YS +prioress/MS +priori +prioritize/DSRGZJ +priority/MS +priory/SM +prise/GMAS +prised +prism/MS +prismatic +prison/DRMSGZ +prisoner/M +prissily +prissiness/SM +prissy/RSPT +pristine/Y +prithee/S +privacy/MS +private/NVYTRSXP +privateer/SMDG +privateness/M +privation/MCS +privative/Y +privatization/S +privatize/GSD +privet/SM +privilege/SDMG +privileged/U +privily +privy/SRMT +prize/DSRGZM +prized/A +prizefight/SRMGJZ +prizefighter/M +prizefighting/M +prizewinner/S +prizewinning +pro/MS +proactive +prob/RBJ +probabilist +probabilistic +probabilistically +probability/SM +probable/S +probably +probate/NVMX +probated/A +probates/A +probating/A +probation's/A +probation/MRZ +probational +probationary/S +probationer/M +probative/A +prober/M +probity/SM +problem/SM +problematic/S +problematical/UY +proboscis/MS +procaine/MS +procedural/SY +procedure/MS +proceed/JRDSG +proceeder/M +proceeding/M +process/BSDMG +processed/UA +processes/A +procession/GD +processional/YS +processor/MS +proclamation/MS +proclivity/MS +proconsular +procrastinate/XNGDS +procrastination/M +procrastinator/MS +procreational +procreatory +procrustean +proctor/GSDM +proctorial +procurable/U +procure/L +procurement/MS +prod/S +prodded +prodding +prodigal/SY +prodigality/S +prodigious/PY +prodigiousness/M +prodigy/MS +produce/AZGDRS +producer/AM +producible/A +product/V +production/ASM +productive/PY +productively/UA +productiveness/MS +productivities +productivity's +productivity/A +productize/GZRSD +prof/S +profanation/S +profane/YPDRSG +profaneness/MS +profanity/MS +professed/Y +profession/SM +professional/USY +professionalism/SM +professionalize/GSD +professor/SM +professorial/Y +professorship/SM +proffer/GSD +proficiency/SM +proficient/YS +profit/GZDRB +profitability/MS +profitable/UP +profitableness/MU +profitably/U +profiteer/GSMD +profiterole/MS +profitless +profligacy/S +profligate/YS +proforma/S +profound/PTYR +profoundity +profoundness/SM +profundity/MS +profuse/YP +profuseness/MS +progenitor/SM +progeny/M +progesterone/SM +prognathous +prognoses +prognosis/M +prognostic/S +prognosticate/NGVXDS +prognostication/M +prognosticator/S +program/CSA +programed +programing +programmability +programmable/S +programmed/CA +programmer/ASM +programming/CA +programmings +progress/MSDVG +progression/SM +progressive/SPY +progressiveness/SM +progressivism +prohibit/VGSRD +prohibiter/M +prohibition/MS +prohibitionist/MS +prohibitive/PY +prohibitiveness/M +prohibitory +project/MDVGS +projected/AU +projectile/MS +projection/MS +projectionist/MS +projective/Y +projector/SM +prolegomena +proletarian/S +proletarianization/M +proletarianized +proletariat/SM +proliferate/GNVDSX +proliferation/M +prolific/P +prolifically +prolix/Y +prolixity/MS +prologize +prologue/MGSD +prologuize +prolong/G +prolongate/NGSDX +prolongation/M +prolonger/M +promenade/GZMSRD +promenader/M +promethium/SM +prominence/MS +prominent/Y +promiscuity/MS +promiscuous/PY +promiscuousness/M +promise/GD +promising/UY +promissory +promontory/MS +promote/GVZBDR +promoter/M +promotive/P +promotiveness/M +prompt/SGJTZPYDR +prompted/U +prompter/M +promptitude/SM +promptness/MS +promulgate/NGSDX +promulgation/M +promulgator/MS +pron +prone/PY +proneness/MS +prong/SGMD +pronghorn/SM +pronominalization +pronominalize +pronounce/GLSRD +pronounceable/U +pronounced/U +pronouncedly +pronouncement/SM +pronouncer/M +pronto +pronunciation/SM +proof/SEAM +proofed/A +proofer +proofing/M +proofread/GZSR +proofreader/M +prop/SZ +propaganda/SM +propagandist/SM +propagandistic +propagandize/DSG +propagate/SDVNGX +propagated/U +propagation/M +propagator/MS +propel/S +propellant/MS +propelled +propeller/MS +propelling +propensity/MS +proper/PYRT +properness/M +propertied/U +property/SDM +prophecy/SM +prophesier/M +prophesy/GRSDZ +prophet/SM +prophetess/S +prophetic +prophetical/Y +prophylactic/S +prophylaxes +prophylaxis/M +propinquity/MS +propionate/M +propitiate/GNXSD +propitiatory +propitious/YP +propitiousness/M +proponent/MS +proportion/ESGDM +proportional/SY +proportionality/M +proportionate/YGESD +proportioner/M +proportionment/M +proposal/SM +propped +propping +proprietary/S +proprietor/SM +proprietorial +proprietorship/SM +proprietress/MS +propriety/MS +proprioception +proprioceptive +propulsion/MS +propulsive +propylene/M +prorogation/SM +prorogue +pros/DSRG +prosaic +prosaically +proscenium/MS +prosciutti +prosciutto/SM +proscription/SM +proscriptive +prose/M +prosecute/SDBXNG +prosecution/M +prosecutor/MS +proselyte/SDGM +proselytism/MS +proselytize/ZGDSR +proser/M +prosodic/S +prosody/MS +prospect/DMSVG +prospection/SM +prospective/SYP +prospectiveness/M +prospector/MS +prospectus/SM +prosper/GSD +prosperity/MS +prosperous/PY +prosperousness/M +prostate +prostheses +prosthesis/M +prosthetic/S +prosthetics/M +prostitute/DSXNGM +prostitution/M +prostrate/SDXNG +prostration/M +prosy/RT +protactinium/MS +protagonist/SM +protean/S +protease/M +protect/DVGS +protected/UY +protection/MS +protectionism/MS +protectionist/MS +protective/YPS +protectiveness/S +protector/MS +protectorate/SM +protein/MS +proteolysis/M +proteolytic +protest/G +protestant/S +protestantism +protestation/MS +protesting/Y +protocol/DMGS +protoplasm/MS +protoplasmic +prototype/SDGM +prototypic +prototypical/Y +protozoa +protozoan/MS +protozoic +protozoon's +protract/DG +protrude/SDG +protrusile +protrusion/MS +protrusive/PY +protuberance/S +protuberant +protégé/SM +protégées +proud/TRY +prov/DRGZB +provabilities +provability's +provability/U +provable/P +provableness/M +provably +prove/ESDAG +proved/U +proven/U +provenance/SM +provender/SDG +provenience/SM +provenly +prover/M +proverb/DG +proverbial/Y +provide/DRSBGZ +provided/U +providence/SM +provident/Y +providential/Y +provider/M +province/SM +provincial/SY +provincialism/SM +provision/R +provisional/YS +provisioner/M +proviso/MS +provocateur/S +provocative/P +provocativeness/SM +provoke/GZDRS +provoked/U +provoking/Y +provolone/SM +provost/MS +prow/TRMS +prowess/SM +prowl/RDSZG +prowler/M +proximal/Y +proximate/PY +proximateness/M +proximity/MS +proxy/SM +prude/MS +prudence/SM +prudent/Y +prudential/SY +prudery/MS +prudish/YP +prudishness/SM +prune/DSRGZM +pruner/M +prurience/MS +prurient/Y +prussic +pry/DRSGTZ +pryer's +prying/Y +précis/MDG +psalm/SGDM +psalmist/SM +psalter +psaltery/MS +psephologist/M +pseudo/S +pseudonym/SM +pseudonymous +pseudopod +pseudoscience/S +pshaw/SDG +psi/S +psittacoses +psittacosis/M +psoriases +psoriasis/M +psst/S +psych/SDG +psyche/M +psychedelic/S +psychedelically +psychiatric +psychiatrist/SM +psychiatry/MS +psychic/MS +psychical/Y +psycho/SM +psychoacoustic/S +psychoacoustics/M +psychoactive +psychoanalysis/M +psychoanalyst/S +psychoanalytic +psychoanalytical +psychoanalyze/SDG +psychobabble/S +psychobiology/M +psychocultural +psychodrama/MS +psychogenic +psychokinesis/M +psycholinguistic/S +psycholinguistics/M +psycholinguists +psychological/Y +psychologist/MS +psychology/MS +psychometric/S +psychometrics/M +psychometry/M +psychoneuroses +psychoneurosis/M +psychopath/M +psychopathic/S +psychopathology/M +psychopaths +psychopathy/SM +psychophysic/S +psychophysical/Y +psychophysics/M +psychophysiology/M +psychos/S +psychosis/M +psychosocial/Y +psychosomatic/S +psychosomatics/M +psychotherapeutic/S +psychotherapist/MS +psychotherapy/SM +psychotic/S +psychotically +psychotropic/S +psychs +pt/C +ptarmigan/MS +pterodactyl/SM +ptomaine/MS +pub/MS +pubbed +pubbing +pubertal +puberty/MS +pubes +pubescence/S +pubescent +pubic +pubis/M +public/YSP +publican/AMS +publication/AMS +publicist/SM +publicity/SM +publicize/SDG +publicized/U +publicness/M +publics/A +publish/JDRSBZG +publishable/U +published/UA +publisher/ASM +publishes/A +publishing/M +puce/SM +puck/GZSDRM +pucker/DG +puckish/YP +puckishness/S +pudding/MS +puddle/JMGRSD +puddler/M +puddling/M +puddly +pudenda +pudendum/M +pudginess/SM +pudgy/PRT +pueblo/SM +puerile/Y +puerility/SM +puerperal +puers +puff/SGZDRM +puffball/SM +puffer/M +puffery/M +puffin/SM +puffiness/S +puffy/PRT +pug/MS +pugged +pugging +pugilism/SM +pugilist/S +pugilistic +pugnacious/YP +pugnaciousness/MS +pugnacity/SM +puissant/Y +puke/GDS +pukka +pulchritude/SM +pulchritudinous/M +pule/GDS +pull/DRGZSJ +pullback/S +pullet/SM +pulley/SM +pullout/S +pullover/SM +pulmonary +pulp/MDRGS +pulpiness/S +pulpit/MS +pulpwood/MS +pulpy/PTR +pulsar/MS +pulsate/NGSDX +pulsation/M +pulse's +pulse/ADSG +pulser +pulverable +pulverization/MS +pulverize/GZSRD +pulverized/U +pulverizer/M +pulverizes/UA +puma/SM +pumice/SDMG +pummel/SDG +pump/GZSMDR +pumpernickel/SM +pumping/M +pumpkin/MS +pun/MS +punch/GRSDJBZ +punchbowl/M +punched/U +puncheon/MS +puncher/M +punchline/S +punchy/RT +punctilio/SM +punctilious/PY +punctiliousness/SM +punctual/PY +punctualities +punctuality/UM +punctualness/M +punctuate/SDXNG +punctuation/M +punctuational +puncture/SDMG +pundit/SM +punditry/S +pungency/MS +pungent/Y +puniness/MS +punish/RSDGBL +punished/U +punisher/M +punishment/MS +punitive/YP +punitiveness/M +punk/TRMS +punky/PRS +punned +punning +punster/SM +punt/GZMDRS +punter/M +puny/PTR +pup/MS +pupa/M +pupae +pupal +pupate/NGSD +pupil/SM +pupillage/M +pupped +puppet/SM +puppeteer/SM +puppetry/MS +pupping +puppy/GSDM +puppyish +purblind +purchasable +purchase/GASD +purchaser/MS +purdah/M +purdahs +pure/PYTGDR +purebred/S +puree/DSM +pureeing +pureness/MS +purgation/M +purgative/MS +purgatorial +purgatory/SM +purge/GZDSR +purger/M +purify/GSRDNXZ +purine/SM +purism/MS +purist/MS +puristic +puritan/SM +puritanic +puritanical/Y +puritanism/S +purity/SM +purl/MDGS +purlieu/SM +purloin/DRGS +purloiner/M +purple/MTGRSD +purplish +purport/DRSZG +purported/Y +purpose/SDVGYM +purposeful/YP +purposefulness/S +purposeless/PY +purposelessness/M +purposive/YP +purposiveness/M +purr/DSG +purring/Y +purse/DSRGZM +purser/M +pursuance/MS +pursuant +pursue/ZGRSD +pursuer/M +pursuit/MS +purulence/MS +purulent +purvey/DGS +purveyance/MS +purveyor/MS +purview/SM +pus/SM +push/DSRBGZ +pushbutton/S +pushcart/SM +pushchair/SM +pushdown +pusher/M +pushily +pushiness/MS +pushover/SM +pushy/PRT +pusillanimity/MS +pusillanimous/Y +puss/S +pussy/TRSM +pussycat/S +pussyfoot/DSG +pustular +pustule/MS +put/IS +putative/Y +putout/S +putrefaction/SM +putrefactive +putrefy/DSG +putrescence/MS +putrescent +putrid/YP +putridity/M +putridness/M +putsch/S +putt/SGZMDR +putted/I +puttee/MS +putter/RDMGZ +putting/I +putty/SDMG +puttying/M +puzzle/JRSDZLG +puzzlement/MS +puzzler/M +pvt +pygmy/SM +pyknotic +pylon/SM +pylori +pyloric +pylorus/M +pyorrhea/SM +pyramid/GMDS +pyramidal/Y +pyre/MS +pyridine/M +pyrimidine/SM +pyrite/MS +pyroelectric +pyroelectricity/SM +pyrolysis/M +pyrolyze/RSM +pyromania/MS +pyromaniac/SM +pyrometer/MS +pyrometry/M +pyrophosphate/M +pyrotechnic/S +pyrotechnical +pyrotechnics/M +pyroxene/M +pyroxenite/M +python/MS +pyx/MDSG +pères +q +q's +qr +qt +qty +qua +quack/SDG +quackery/MS +quackish +quad/SM +quadded +quadding +quadrangle/MS +quadrangular/M +quadrant/MS +quadraphonic/S +quadrapole +quadratic/SM +quadratical/Y +quadrature/MS +quadrennial/SY +quadrennium/MS +quadric +quadriceps/SM +quadrilateral/S +quadrille/XMGNSD +quadrillion/MH +quadripartite/NY +quadriplegia/SM +quadriplegic/SM +quadrivia +quadrivium/M +quadruped/MS +quadrupedal +quadruple/GSD +quadruplet/SM +quadruplicate/GDS +quadruply/NX +quadrupole +quadword/MS +quaff/SRDG +quaffer/M +quagmire/DSMG +quahog/MS +quail/GSDM +quaint/PTYR +quaintness/MS +quake/GZDSR +quaky/RT +qualification/ME +qualified/UY +qualifier/SM +qualify/EGXSDN +qualitative/Y +quality/MS +qualm/SM +qualmish +quandary/MS +quangos +quanta/M +quantifiable/U +quantified/U +quantifier/M +quantify/GNSRDZX +quantile/S +quantitative/PY +quantitativeness/M +quantity/MS +quantization/MS +quantize/ZGDRS +quantizer/M +quantum/M +quarantine/DSGM +quark/SM +quarrel/SZDRMG +quarreler/M +quarrellings +quarrelsome/PY +quarrelsomeness/MS +quarrier/M +quarry/RSDGM +quarryman/M +quarrymen +quart/RMSZ +quarter/MDRYG +quarterback/SGMD +quarterdeck/MS +quarterer/M +quarterfinal/MS +quartering/M +quarterly/S +quartermaster/MS +quarterstaff/M +quarterstaves +quartet/SM +quartic/S +quartile/SM +quarto/SM +quartz/SM +quartzite/M +quasar/SM +quash/GSD +quasi +quasilinear +quaternary/S +quaternion/SM +quatrain/SM +quaver/GDS +quavering/Y +quavery +quay/SM +quayside/M +queasily +queasiness/SM +queasy/TRP +queen/SGMDY +queenly/RT +queer/STGRDYP +queerness/S +quell/SRDG +queller/M +quench/GZRSDB +quenchable/U +quenched/U +quencher/M +quenchless +quern/M +querulous/YP +querulousness/S +query/MGRSD +quest/FSIM +quested/A +quester's +quester/AS +questing +question/SMRDGBZJ +questionable/P +questionableness/M +questionably/U +questioned/UA +questioner/M +questioning/UY +questionnaire/MS +quests/A +queue/GZMDSR +queued/C +queuer/M +queues/C +queuing/C +quibble/GZRSD +quibbler/M +quiche/SM +quick/RNYTXPS +quicken/RDG +quickie/MS +quicklime/SM +quickness/MS +quicksand/MS +quicksilver/GDMS +quickstep/SM +quid/SM +quiesce/D +quiescence/MS +quiescent/YP +quiet/UTGPSDRY +quieted/E +quieten/SGD +quieter's +quieter/E +quieting/E +quietly/E +quietness/MS +quiets/E +quietude/IEMS +quietus/MS +quill/GSDM +quilt/SZJGRDM +quilter/M +quilting/M +quince/SM +quincentenary/M +quincy/M +quinine/MS +quinquennial/Y +quinsy/SM +quint/MS +quintessence/SM +quintessential/Y +quintet/SM +quintic +quintile/SM +quintillion/MH +quintillionth/M +quintuple/SDG +quintuplet/MS +quip/MS +quipped +quipper +quipping +quipster/SM +quire/MDSG +quired/AI +quires/AI +quiring/IA +quirk/SGMD +quirkiness/SM +quirky/PTR +quirt/SDMG +quisling/SM +quit/DGS +quitclaim/GDMS +quite/SADG +quittance/SM +quitter/SM +quitting +quiver/GDS +quivering/Y +quivery +quixotic +quixotically +quiz/M +quizzed +quizzer/SM +quizzes +quizzical/Y +quizzing +quo/H +quoin/SGMD +quoit/GSDM +quondam +quonset +quorate/I +quorum/MS +quot/GDRB +quota/MS +quotability/S +quotation/SM +quote/UGSD +quoter/M +quotidian/S +quotient/SM +qwerty +qwertys +r's +r/TGVJ +rabbet/GSMD +rabbi/MS +rabbinate/MS +rabbinic +rabbinical/Y +rabbit/MRDSG +rabbiter/M +rabble/GMRSD +rabbler/M +rabid/YP +rabidness/SM +rabies +rabis +raccoon/SM +race/MZGDRSJ +racecourse/MS +racegoers +racehorse/SM +raceme/MS +racer/M +racetrack/SMR +raceway/SM +racial/Y +racialism/MS +racialist/MS +racily +raciness/MS +racism/S +racist/MS +rack/GDRMS +racket/SMDG +racketeer/MDSJG +rackety +raconteur/SM +racoon's +racquet's +racquetball/S +racy/RTP +rad/S +radar/SM +radarscope/MS +radded +radder +raddest +radding +radial/SY +radian/SM +radiance/SM +radiant/YS +radiate/XSDYVNG +radiation/M +radiative/Y +radiator/MS +radical/SPY +radicalism/MS +radicalization/S +radicalize/GSD +radicalness/M +radices's +radii/M +radio/SMDG +radioactive/Y +radioactivity/MS +radioastronomical +radioastronomy +radiocarbon/MS +radiochemical/Y +radiochemistry/M +radiogalaxy/S +radiogram/SM +radiographer/MS +radiographic +radiography/MS +radioisotope/SM +radiologic +radiological/Y +radiologist/MS +radiology/MS +radioman/M +radiomen +radiometer/SM +radiometric +radiometry/MS +radionics +radionuclide/M +radiopasteurization +radiophone/MS +radiophysics +radioscopy/SM +radiosonde/SM +radiosterilization +radiosterilized +radiotelegraph +radiotelegraphs +radiotelegraphy/MS +radiotelephone/SM +radiotherapist/SM +radiotherapy/SM +radish/MS +radium/MS +radius/M +radix/SM +radon/SM +raffia/SM +raffish/PY +raffishness/SM +raffle/MSDG +raft/GZSMDR +rafter/DM +rag/GSMD +raga/MS +ragamuffin/MS +ragbag/SM +rage/MS +ragged/PRYT +raggedness/SM +raggedy/TR +ragging +raging/Y +raglan/MS +ragout/SMDG +ragtag/MS +ragtime/MS +ragweed/MS +ragwort/M +rah/DG +rahs +raid/MDRSGZ +raider/M +rail's +rail/CDGS +railbird/S +railer/SM +railhead/SM +railing/MS +raillery/MS +railroad/SZRDMGJ +railroader/M +railroading/M +railway/MS +railwaymen +raiment/SM +rain/GSDM +rainbow/MS +raincloud/S +raincoat/SM +raindrop/SM +rainfall/SM +rainforest's +rainless +rainmaker/SM +rainmaking/MS +rainproof/GSD +rainstorm/SM +rainwater/MS +rainy/RT +raise/DSRGZ +raiser/M +raisin/MS +raising/M +raj/M +rajah/M +rajahs +rake/MGDRS +raker/M +rakish/PY +rakishness/MS +rally/GSD +ram/SM +ramble/JRSDGZ +rambler/M +rambling/Y +rambunctious/PY +rambunctiousness/S +ramekin/SM +ramie/MS +ramification/M +ramify/XNGSD +ramjet/SM +rammed +ramming +ramp/GMDS +rampage/SDG +rampancy/S +rampant/Y +rampart/SGMD +ramrod/MS +ramrodded +ramrodding +rams/S +ramshackle +ran/A +ranch/ZRSDMJG +rancher/M +rancho/SM +rancid/P +rancidity/MS +rancidness/SM +rancor/SM +rancorous/Y +rand/MDGS +randiness/S +random/PYS +randomization/SM +randomize/SRDG +randomness/SM +randy/PRST +ranee/SM +rang/GZDR +range/SM +ranged/C +rangeland/S +ranger/M +ranges/C +ranginess/S +ranging/C +rangy/RPT +rani's +rank/GZTYDRMPJS +ranked/U +ranker/M +ranking/M +rankle/SDG +rankness/MS +ransack/GRDS +ransacker/M +ransom/ZGMRDS +ransomer/M +rant/GZDRJS +ranter/M +ranting/Y +rap/MDRSZG +rapacious/YP +rapaciousness/MS +rapacity/MS +rape/SM +rapeseed/M +rapid/YRPST +rapidity/MS +rapidness/S +rapier/SM +rapine/SM +rapist/MS +rapped +rappel/S +rappelled +rappelling +rapper/SM +rapping/M +rapport/SM +rapporteur/SM +rapprochement/SM +rapscallion/MS +rapt/YP +raptness/S +rapture/MGSD +rapturous/YP +rapturousness/M +rare/YTPGDRS +rarebit/MS +rarefaction/MS +rarefy/GSD +rareness/MS +rarity/SM +rascal/SMY +rash/PZTYSR +rasher/M +rashness/S +rasp/SGJMDR +raspberry/SM +rasper/M +rasping/Y +raspy/RT +raster/MS +rat/MDRSJZGB +ratchet/MDSG +rate's +rate/KNGSD +rateable +rated/U +ratepayer/SM +rater/M +rather +rathskeller/SM +ratifier/M +ratify/ZSRDGXN +rating/M +ratio/MS +ratiocinate/VNGSDX +ratiocination/M +ration/DSMG +rational/YPS +rationale/SM +rationalism/SM +rationalist/S +rationalistic +rationality/MS +rationalization/SM +rationalize/ZGSRD +rationalizer/M +rationalness/M +ratlike +ratline/SM +rattail +rattan/MS +ratted +ratter/MS +ratting +rattle/RSDJGZ +rattlebrain/DMS +rattlesnake/MS +rattletrap/MS +rattling/Y +rattly/TR +rattrap/SM +ratty/RT +raucous/YP +raucousness/SM +raunchily +raunchiness/S +raunchy/RTP +ravage/GZRSD +ravager/M +rave/ZGDRSJ +ravel/UGDS +raveling/S +raven/JGMRDS +ravenous/YP +raver/M +ravine/SDGM +ravioli/SM +ravish/LSRDZG +ravisher/M +ravishing/Y +ravishment/SM +raw/PSRYT +rawboned +rawhide/SDMG +rawness/SM +ray/GSMD +rayon/SM +raze/DRSG +razer/M +razor/MDGS +razorback/SM +razorblades +razz/GDS +razzmatazz/S +rcpt +rd +re/YM +reabbreviate +reach/GRB +reachability +reachable/U +reachably +reached/U +reacher/M +reacquisition +reactant/SM +reacted/U +reaction +reactionary/SM +reactivity +read/JGZBR +readability/MS +readable/P +readably +readdress/G +reader/M +readership/MS +readied +readies +readily +readiness/UM +readinesses +reading/M +readopt/G +readout/MS +reads/A +ready/TUPR +readying +real/RSTP +realism's +realism/U +realisms +realist/SM +realistic/U +realistically/U +reality/USM +realizability/MS +realizable/SMP +realizableness/M +realizably/S +realization/MS +realize/JRSDBZG +realized/U +realizer/M +realizes/U +realizing/MY +realm/M +realness/S +realpolitik/SM +realtor's +realty/SM +ream/MDRGZ +reamer/M +reanimate +reap/SGZ +reaper/M +reappraise/G +rear/DRMSG +rearguard/MS +rearmost +rearrange/L +rearward/S +reason/UBDMG +reasonable/UP +reasonableness/SMU +reasonably/U +reasoner/SM +reasoning/MS +reasonless +reasons +reassess/GL +reassuringly/U +reattach/GSL +reawakening/M +rebate/M +rebel/MS +rebeller +rebellion/SM +rebellious/YP +rebelliousness/MS +rebid +rebidding +rebind/G +rebirth +reboil/G +rebook +reboot/ZR +rebound/G +rebroadcast/MG +rebuke/RSDG +rebuking/Y +rebus +rebuttal/SM +rebutting +rec +rec'd +recalcitrance/SM +recalcitrant/S +recalibrate/N +recant/G +recantation/S +recap +recappable +recapping +recast/G +recd +recede +receipt/SGDM +receivable/S +receive/ZGRSDB +received/U +receiver/M +receivership/SM +recency/M +recension/M +recent/YPT +recentness/SM +receptacle/SM +reception/MS +receptionist/MS +receptive/YP +receptiveness/S +receptivity/S +receptor/MS +recess/SDMVG +recessional/S +recessionary +recessive/YPS +recessiveness/M +rechargeable +recheck/G +recherches +recherché +recidivism/MS +recidivist/MS +recipe/MS +recipiency +recipient/MS +reciprocal/SY +reciprocate/NGXVDS +reciprocation/M +reciprocity/MS +recital/MS +recitalist/S +recitative/MS +recite/ZR +reciter/M +recked +recking +reckless/PY +recklessness/S +reckon/SGRDJ +reckoner/M +reckoning/M +reclaim/B +reclamation/SM +recline/RSDZG +recliner/M +recluse/MVNS +reclusion/M +recode/G +recognizability +recognizable/U +recognizably +recognize/BZGSRD +recognized/U +recognizedly/S +recognizer/M +recognizing/UY +recognizingly/S +recoilless +recoinage +recolor/GD +recombinant +recombine +recommended/U +recompense/GDS +recompute/B +reconcile/SRDGB +reconciled/U +reconciler/M +recondite/YP +reconditeness/M +reconfigurability +reconfigure/R +reconnaissance/MS +reconnect/R +reconnoiter/GSD +reconquer/G +reconsecrate +reconstitute +reconstructed/U +reconsult/G +recontact/G +recontaminate/N +recontribute +recook/G +recopy/G +record/ZGJ +recorded/AU +records/A +recourse +recover/B +recoverability +recoverable/U +recovery/MS +recreant/S +recreational +recriminate/GNVXDS +recrimination/M +recriminatory +recross/G +recrudesce/GDS +recrudescence/MS +recrudescent +recruit/ZSGDRML +recruiter/M +recruitment/MS +recrystallize +recta's +rectal/Y +rectangle/SM +rectangular/Y +rectifiable +rectification/M +rectifier/M +rectify/DRSGXZN +rectilinear/Y +rectitude/MS +recto/MS +rector/SM +rectory/MS +rectum/SM +recumbent/Y +recuperate/VGNSDX +recuperation/M +recur +recurrence/MS +recurrent +recurse/NX +recursion/M +recusant/M +recuse +recyclable/S +recycle/BZ +red/PYS +redact/DGS +redaction/SM +redactor/MS +redbird/SM +redbreast/SM +redbrick/M +redbud/M +redcap/MS +redcoat/SM +redcurrant/M +redden/DGS +redder +reddest +redding +reddish/P +redeclaration +redecorate +redeem/BRZ +redeemable/U +redeemed/U +redeemer/M +redemption/RMS +redemptioner/M +redemptive +redeposit/M +redetermination +redhead/DRMS +redial/G +redirect/G +redirection +redlining/S +redneck/SMD +redness/MS +redo/G +redolence/MS +redolent +redouble/S +redoubtably +redound/GDS +redshift/S +redskin/SM +reduce/RSDGZ +reduced/U +reducer/M +reducibility/M +reducible +reducibly +reduct/V +reduction/SM +reductionism/M +reductionist/S +redundancy/SM +redundant/Y +redwood/SM +redye +redyeing +reecho/G +reed/GMDR +reediness/SM +reeding/M +reedy/PTR +reef/GZSDRM +reefer/M +reek/GSR +reeker/M +reel's +reel/USDG +reeler/M +reenforcement +reentrant +reestimate/M +reeve/G +reexamine +ref/ZS +refection/SM +refectory/SM +refer/B +referee/MSD +refereed/U +refereeing +reference's +reference/CGSRD +referenced/U +referencing/U +referendum/MS +referent/SM +referential/YM +referentiality +referral/SM +referred +referrer/S +referring +reffed +reffing +refile +refinance +refine/LZ +refined/U +refinement/MS +refinish/G +refit +reflect/SDGV +reflectance/M +reflected/U +reflection/SM +reflectional +reflective/YP +reflectiveness/M +reflectivity/M +reflector/MS +reflex/YV +reflexion/MS +reflexive/PSY +reflexiveness/M +reflexivity/M +reflooring +refluent +reflux/G +refocus/G +refold/G +reforestation +reforge/G +reform/B +reformatory/SM +reformed/U +reformer/M +reformism/M +reformist/S +refract/DGVS +refractive/PY +refractiveness/M +refractometer/MS +refractoriness/M +refractory/PS +refrain/DGS +refresh/LB +refreshed/U +refreshing/Y +refreshment/MS +refrigerant/MS +refrigerate/XDSGN +refrigerated/U +refrigeration/M +refrigerator/MS +refrozen +refry/GS +refuge/SDGM +refugee/MS +refulgence/SM +refulgent +refund/B +refunder/M +refurbish/L +refurbishment/S +refusal/SM +refuse/R +refuser/M +refutation/MS +refute/GZRSDB +refuter/M +reg +regal/GYRD +regale/L +regalement/S +regalia/M +regard/EGDS +regardless/PY +regather/G +regatta/MS +regency/MS +regeneracy/MS +regenerate/U +regenerately +regenerateness/M +reggae/SM +regicide/SM +regime/MS +regimen/MS +regiment/SDMG +regimental/S +regimentation/MS +region/SM +regional/SY +regionalism/MS +register's +register/UDSG +registrable +registrant/SM +registrar/SM +registration/AM +registrations +registry/MS +regnant +regress/DSGV +regression/MS +regressive/PY +regressiveness/M +regressors +regret/S +regretful/PY +regretfulness/M +regrettable +regrettably +regretted +regretting +reground +regroup/G +regrow/G +regular/YS +regularity/MS +regularization/MS +regularize/SDG +regulate/CSDXNG +regulated/U +regulation/M +regulative +regulator/SM +regulatory +regurgitate/XGNSD +regurgitation/M +rehab/S +rehabbed +rehabbing +rehabilitate/SDXVGN +rehabilitation/M +rehang/G +rehear/GJ +rehears/R +rehearsal/SM +rehearse +rehearsed/U +rehearser/M +reheat/G +reheating/M +rehydrate +reign/MDSG +reimburse/GSDBL +reimbursement/MS +rein/GDM +reindeer/M +reinforce/GSRDL +reinforced/U +reinforcement/MS +reinforcer/M +reinstate/L +reinstatement/MS +reinsurance +reissue +reiterative/SP +reject/RDVGS +rejecter/M +rejecting/Y +rejection/SM +rejector/MS +rejigger +rejoice/RSDJG +rejoicing/Y +rejoinder/SM +rejuvenate/NGSDX +rejuvenatory +rel/V +relapse +relate/XVNGSZ +related/U +relatedly +relatedness/MS +relater/M +relation/M +relational/Y +relationship/MS +relative/SPY +relativeness/M +relativism/M +relativist/MS +relativistic +relativistically +relativity/MS +relator's +relax/GZD +relaxant/SM +relaxation/MS +relaxed/YP +relaxedness/M +relaxing/Y +relay/GDM +relearn/G +releasable/U +release/B +released/U +relent/SDG +relenting/U +relentless/PY +relentlessness/SM +relevance/SM +relevancy/MS +relevant/Y +reliability/UMS +reliable/U +reliables +reliably/U +reliance/MS +reliant/Y +relic/MS +relicense/R +relict's +relict/C +relief/M +relieve/RSDZG +relieved/U +relievedly +reliever/M +religion/SM +religionists +religiosity/M +religious/PY +religiousness/MS +relink/G +relinquish/GSDL +relinquishment/SM +reliquary/MS +relish/GSD +relive/GB +reload/GR +relocate/B +reluctance/MS +reluctant/Y +rely/DG +rem +remade/S +remain/GD +remainder/SGMD +remake/M +remand/DGS +remap +remapping +remark/BG +remarkable/U +remarkableness/S +remarkably +remarked/U +rematch/G +remeasure/D +remediable/P +remediableness/M +remedy/SDMG +remember/GR +remembered/U +rememberer/M +remembrance/MRS +remembrancer/M +reminisce/GSD +reminiscence/SM +reminiscent/Y +remiss/YP +remissness/MS +remit/S +remittance/MS +remitted +remitting/U +remnant/MS +remodel/G +remolding +remonstrant/MS +remonstrate/SDXVNG +remonstration/M +remonstrative/Y +remorse/SM +remorseful/PY +remorsefulness/M +remorseless/YP +remorselessness/MS +remote/RPTY +remoteness/MS +remoulds +removal/MS +remunerate/VNGXSD +remunerated/U +remuneration/M +remunerative/YP +remunerativeness/M +renaissance/S +renal +renaturation +rend +rend/RGZS +render/GJRD +renderer/M +rendering/M +rendezvous/DSMG +rendition/GSDM +renegade/SDMG +renege/GZRSD +reneger/M +renew/BG +renewal/MS +renewer/M +rennet/MS +rennin/SM +renounce/LGRSD +renouncement/MS +renouncer/M +renovate/NGXSD +renovation/M +renovator/SM +renown/SGDM +rent/GZMDRS +rental/SM +rentaller +renter/M +renumber/G +renumeration +renunciate/VNX +renunciation/M +reoccupy/G +reopen/G +reorganized/U +rep/S +repack/G +repair/BZGR +repairable/U +repairer/M +repairman/M +repairmen +repairs/E +repaper +reparable +reparation/SM +repartee/MDS +reparteeing +repartition/Z +repast/G +repatriate/SDXNG +repave +repeal/GR +repealer/M +repeat/RDJBZG +repeatability/M +repeatable/U +repeatably +repeated/Y +repeater/M +repel/S +repelled +repellent/SY +repelling/Y +repent/RDG +repentance/SM +repentant/SY +repertoire/SM +repertory/SM +repetition +repetitious/YP +repetitiousness/S +repetitive/PY +repetitiveness/MS +repine/R +repiner/M +replace/RL +replay/GM +replenish/LRSDG +replenishment/S +replete/SDPXGN +repleteness/MS +repletion/M +replica/SM +replicate/SDVG +replicator/S +replug +reply/X +repopulate +reported/Y +reportorial/Y +repose/M +reposeful +repository/MS +reprehend/GDS +reprehensibility/MS +reprehensible/P +reprehensibleness/M +reprehensibly +reprehension/MS +represent/GB +representable/U +representational/Y +representative/SYMP +representativeness/M +representativity +represented/U +repress/V +repression/SM +repressive/YP +repressiveness/M +reprieve/GDS +reprimand/SGMD +reprint/M +reprisal/MS +reproach/GRSDB +reproacher/M +reproachful/YP +reproachfulness/M +reproaching/Y +reprobate/N +reprocess/G +reproducibility/MS +reproducible/S +reproducibly +reproductive/S +reproof/G +reprove/R +reproving/Y +reptile/SM +reptilian/S +republic/M +republicanism/SM +republish/G +repudiate/XGNSD +repudiation/M +repudiator/S +repugnance/MS +repugnant/Y +repulse/VNX +repulsion/M +repulsive/PY +repulsiveness/MS +reputability/SM +reputably/E +reputation/SM +repute/ESB +reputed/Y +reputing +request/G +requested/U +requiem/SM +require/LR +requirement/MS +requisite/PNXS +requisiteness/M +requisition/GDRM +requisitioner/M +requital/MS +requite/RZ +requited/U +requiter/M +reread/G +rerecord/G +rerouteing +rerunning +res/C +rescale +rescind/SDRG +rescission/SM +rescue/GZRSD +reseal/BG +research/MB +reselect/G +resemblant +resemble/DSG +resend/G +resent/DSLG +resentful/PY +resentfulness/SM +resentment/MS +reserpine/MS +reservation/MS +reserved/UYP +reservedness/UM +reservednesses +reservist/SM +reservoir/MS +reset/RDG +resettle/L +reshipping +reshow/G +reshuffle/M +reside/G +residence/MS +residency/SM +resident/SM +residential/Y +resider/M +residua +residual/YS +residuary +residue/SM +residuum/M +resignation/MS +resigned/YP +resilience/MS +resiliency/S +resilient/Y +resin/D +resinlike +resinous +resiny +resist/RDZVGS +resistance/SM +resistant/U +resistantly +resistants +resisted/U +resistible +resistibly +resisting/U +resistive/PY +resistiveness/M +resistivity/M +resistless +resistor/MS +resize/G +resold +resole/G +resoluble +resolute/PYTRV +resoluteness/MS +resolvability/M +resolvable/U +resolved/U +resolvent +resonance/SM +resonant/YS +resonate/DSG +resonator/MS +resorption/MS +resort/R +resound/G +resourceful/PY +resourcefulness/SM +resp +respect's/E +respect/BSDRMZGV +respectability/SM +respectable/SP +respectably +respected/E +respectful/EY +respectfulness/SM +respecting/E +respective/PY +respectiveness/M +respects/E +respell/G +respiration/MS +respirator/SM +respiratory/M +resplendence/MS +resplendent/Y +respond/SDRZG +respondent/MS +response/RSXMV +responser/M +responsibility/MS +responsible/P +responsibleness/M +responsibly +responsive/YPU +responsiveness/MSU +respray/G +rest's/U +rest/DRSGVM +restart/B +restate/L +restaurant/SM +restaurateur/SM +rested/U +rester/M +restful/YP +restfuller +restfullest +restfulness/MS +restitution/SM +restive/PY +restiveness/SM +restless/YP +restlessness/MS +restorability +restoration/MS +restorative/PYS +restore/Z +restorer/M +restrained/UY +restraint/MS +restrict/DVGS +restricted/YU +restriction/SM +restrictive/U +restrictively +restrictiveness/MS +restrictives +restroom/SM +restructurability +restructure +rests/U +restudy/M +restyle +resubstitute +result/SGMD +resultant/YS +resume/SDBG +resumption/MS +resurface +resurgence/MS +resurgent +resurrect/GSD +resurrection/SM +resurvey/G +resuscitate/XSDVNG +resuscitation/M +resuscitator/MS +retail/Z +retain/LZGSRD +retainer/M +retake +retaliate/VNGXSD +retaliation/M +retaliatory +retard/ZGRDS +retardant/SM +retardation/SM +retarder/M +retch/SDG +retention/SM +retentive/YP +retentiveness/S +retentivity/M +retest/G +rethought +reticence/S +reticent/Y +reticle/SM +reticular +reticulate/GNYXSD +reticulation/M +reticule/MS +reticulum/M +retina/SM +retinal/S +retinue/MS +retire/L +retiredness/M +retiree/MS +retirement/SM +retiring/YP +retort/GD +retract/DG +retractile +retrench/L +retrenchment/MS +retributed +retribution/MS +retributive +retrieval/SM +retrieve/ZGDRSB +retriever/M +retro/SM +retroactive/Y +retrofire/GMSD +retrofit/S +retrofitted +retrofitting +retroflection +retroflex/D +retroflexion/M +retrogradations +retrograde/GYDS +retrogress/SDVG +retrogression/MS +retrogressive/Y +retrorocket/MS +retrospect/SVGMD +retrospection/MS +retrospective/SY +retrovirus/S +retrovision +retry/G +retsina/SM +returnable/S +returned/U +returnee/SM +retype +reuse/B +reutilization +rev/ZM +revanchist +reveal/JBG +revealed/U +revealing/U +revealingly +reveille/MS +revel/SJRDGZ +revelation/MS +revelatory +revelry/MS +revenge/MGSRD +revenger/M +revenue/ZR +revenuer/M +reverberant +reverberate/XVNGSD +reverberation/M +revere/GSD +reverence/SRDGM +reverencer/M +reverend/SM +reverent/Y +reverential/Y +reverie/SM +revers/M +reversal/MS +reverse/Y +reverser/M +reversibility/M +reversible/S +reversibly +reversion/R +reversioner/M +revert/RDVGS +reverter/M +revertible +revet/L +revetment/SM +review/G +revile/GZSDL +revilement/MS +reviler/M +revise/BRZ +revised/U +revisionary +revisionism/SM +revisionist/SM +revitalize/ZR +revival/SM +revivalism/MS +revivalist/MS +revive/RSDG +reviver/M +revivification/M +revivify/X +revocable +revoke/GZRSD +revolt/GRD +revolter/M +revolting/Y +revolution/SM +revolutionariness/M +revolutionary/MSP +revolutionist/MS +revolutionize/GDSRZ +revolutionizer/M +revolve/BSRDZJG +revolver/M +revue/MS +revulsion/MS +revved +revving +rewarded/U +rewarding/Y +rewarm/G +reweave +rewedding +reweigh/G +rewind/BGR +rewire/G +rework/G +rexes +rezone +rhapsodic +rhapsodical +rhapsodize/GSD +rhapsody/SM +rhea/SM +rhenium/MS +rheology/M +rheostat/MS +rhesus/S +rhetoric/MS +rhetorical/YP +rhetorician/MS +rheum/MS +rheumatic/S +rheumatically +rheumatics/M +rheumatism/SM +rheumatoid +rheumy/RT +rhinestone/SM +rhinitides +rhinitis/M +rhino/MS +rhinoceros/MS +rhinotracheitis +rhizome/MS +rho/MS +rhodium/MS +rhododendron/SM +rhodolite/M +rhodonite/M +rhombic +rhomboid/SM +rhomboidal +rhombus/SM +rhubarb/MS +rhyme/DSRGZM +rhymester/MS +rhythm/MS +rhythmic/S +rhythmical/Y +rhythmics/M +rial/MS +rib/MS +ribald/S +ribaldry/MS +ribbed +ribber/S +ribbing/M +ribbon/DMSG +ribcage +riboflavin/MS +ribonucleic +ribosomal +ribosome/MS +rice/DRSMZG +ricer/M +rich/YNSRPT +richen/DG +richness/MS +rick/GSDM +rickets/M +rickety/RT +rickrack/MS +rickshaw/SM +ricochet/GSD +ricotta/MS +rid/ZGRJSB +riddance/SM +ridden +ridding +riddle/GMRSD +ride/CZSGR +rider/CM +riderless +ridership/S +ridge/DSGM +ridgepole/SM +ridgy/RT +ridicule/MGDRS +ridiculer/M +ridiculous/PY +ridiculousness/MS +riding/M +rife/RT +riff/GSDM +riffle/SDG +riffraff/SM +rifle/GZMDSR +rifled/U +rifleman/M +riflemen +rifler/M +rifling/M +rift/GSMD +rig/MS +rigamarole's +rigatoni/M +rigged +rigger/SM +rigging/MS +right/SGTPYRDN +righteous/PYU +righteousness/MS +righteousnesses/U +rightful/PY +rightfulness/MS +rightism/SM +rightist/S +rightmost +rightness/MS +rights/M +rightsize/SDG +rightward/S +rigid/YP +rigidify/S +rigidity/S +rigidness/S +rigmarole/MS +rigor/MS +rigorous/YP +rigorousness/S +rile/DSG +rill/GSMD +rim/GSMDR +rime/MS +rimer/M +rimless +rimmed +rimming +rind/MDGS +ring/GZJDRM +ringer/M +ringing/Y +ringleader/MS +ringlet/SM +ringlike +ringmaster/MS +ringside/ZMRS +ringworm/SM +rink/GDRMS +rinse/DSRG +riot/SMDRGZJ +rioter/M +riotous/PY +riotousness/M +rip/NDRSXTG +riparian/S +ripcord/SM +ripe/PSY +ripen/RDG +ripened/U +ripeness/UM +ripenesses +riper/U +ripest/U +ripoff/S +riposte/SDMG +ripped +ripper/SM +ripping +ripple/RSDGM +rippler/M +ripply/TR +ripsaw/GDMS +riptide/SM +rise/RSJZG +risen +riser/M +risibility/SM +risible/S +rising/M +risk/GSDRM +risker/M +riskily +riskiness/MS +risky/RTP +risotto/SM +risqué +rissole/M +rite/DSM +ritual/MSY +ritualism/SM +ritualistic +ritualistically +ritualized +ritzy/TR +riv/ZGNDR +rival/SGDM +rivaled/U +rivalry/MS +rive/CSGRD +river/CM +riverbank/SM +riverbed/S +riverboat/S +riverfront +riverine +riverside/S +rivet/GZSRDM +riveter/M +riveting/Y +rivulet/SM +riyal/SM +rm +roach/GSDM +road/MIS +roadbed/MS +roadblock/SMDG +roadhouse/SM +roadie/S +roadkill/S +roadrunner/MS +roadshow/S +roadside/S +roadsigns +roadster/SM +roadsweepers +roadway/SM +roadwork/SM +roadworthy +roam/DRGZS +roan/S +roar/DRSJGZ +roarer/M +roaring/T +roast/SGJZRD +roaster/M +rob/SDG +robbed +robber/SM +robbery/SM +robbing +robe's +robe/ESDG +robin/MS +robot/MS +robotic/S +robotism +robotize/GDS +robust/RYPT +robustness/SM +rock/GZDRMS +rockabilly/MS +rockabye +rockbound +rocker/M +rocket/SMDG +rocketry/MS +rockfall/S +rockiness/MS +rocky/SRTP +rococo/MS +rod/SGMD +rodded +rodder +rodding +rode/S +rodent/MS +rodeo/SMDG +roe/SM +roebuck/SM +roentgen/SM +roger/GSD +rogue/GMDS +rogued/K +roguery/MS +rogues/K +roguing/K +roguish/PY +roguishness/SM +roil/SGD +roister/SZGRD +roisterer/M +role/MS +roll/UDSG +rollback/SM +rolled/A +roller/SM +rollerskating +rollick/DGS +rollicking/Y +rolling/S +rollover/S +romaine/MS +roman/S +romance/RSDZMG +romancer/M +romantic/MS +romantically/U +romanticism/MS +romanticist/S +romanticize/SDG +romeo/S +romp/GSZDR +romper/M +rondo/SM +rood/MS +roof/DRMJGZS +roofer/M +roofgarden +roofing/M +roofless +rooftop/S +rook/GDMS +rookery/MS +rookie/SRMT +room/MDRGZS +roomer/M +roomette/SM +roomful/MS +roominess/MS +roommate/SM +roomy/TPSR +roost/SGZRDM +rooster/M +root/MGDRZS +rooted/P +rooter/M +rootless/P +rootlessness/M +rootlet/SM +rootstock/M +rope/DRSMZG +roper/M +roping/M +rosary/SM +rose/MGDS +roseate/Y +rosebud/MS +rosebush/SM +rosemary/MS +rosette/SDMG +rosewater +rosewood/SM +rosily +rosin/SMDG +rosiness/MS +roster/DMGS +rostra's +rostrum/SM +rosy/RTP +rot/SDG +rota/MS +rotary/S +rotate/VGNXSD +rotated/U +rotation/M +rotational/Y +rotative/Y +rotator/SM +rotatory +rote/MS +rotgut/MS +rotisserie/MS +rotogravure/SM +rotor/MS +rototill/RZ +rotted +rotten/RYSTP +rottenness/S +rotter/M +rotting +rotund/SDYPG +rotunda/SM +rotundity/S +rotundness/S +rouge/GMDS +rough/XPYRDNGT +roughage/SM +roughen/DG +rougher/M +roughhouse/GDSM +roughish +roughneck/MDSG +roughness/MS +roughs +roughshod +roulette/MGDS +round/YRDSGPZT +roundabout/PSM +rounded/P +roundedness/M +roundelay/SM +roundels +rounder/M +roundhead/D +roundheaded/P +roundheadedness/M +roundhouse/SM +roundish +roundness/MS +roundoff +roundup/MS +roundworm/MS +rouse/DSRG +rouser/M +roust/SGD +roustabout/SM +rout/GZJMDRS +route's +route/ASRDZGJ +router/M +routine/SYM +routing/M +routinize/GSD +roué/MS +rove/ZGJDRS +rover/M +roving/M +row/SJZMGNDR +rowboat/SM +rowdily +rowdiness/MS +rowdy/PTSR +rowdyism/MS +rowel/DMSG +rowen/M +rower/M +royal/SY +royalist/SM +royalty/MS +rpm +rps +rs +rt +rte +rub/S +rubato/MS +rubbed +rubber/SDMG +rubberize/GSD +rubberneck/DRMGSZ +rubbery/TR +rubbing/M +rubbish/DSMG +rubbishy +rubble/GMSD +rubdown/MS +rube/SM +rubella/MS +rubicund +rubidium/SM +ruble/MS +rubout +rubric/MS +ruby/MTGDSR +ruck/M +rucksack/SM +ruckus/SM +ruction/SM +rudder/MS +rudderless +ruddiness/MS +ruddy/PTGRSD +rude/PYTR +rudeness/MS +rudiment/SM +rudimentariness/M +rudimentary/P +rue/GDS +rueful/PY +ruefulness/S +ruff/GSYDM +ruffian/GSMDY +ruffle/RSDG +ruffled/U +ruffler/M +ruffly/TR +rug/MS +rugby/SM +rugged/PYRT +ruggedness/S +rugging +ruin/MGSDR +ruination/MS +ruiner/M +ruinous/YP +ruinousness/M +rule/MZGJDRS +rulebook/S +ruled/U +ruler/GMD +ruling/M +rum/XSMN +rumba/GDMS +rumble/JRSDG +rumbler/M +rumbustious +rumen/M +ruminant/YMS +ruminate/VNGXSD +ruminative/Y +rummage/GRSD +rummager/M +rummer +rummest +rummy/TRSM +rumor/ZMRDSG +rumored/U +rumorer/M +rumormonger/SGMD +rump/GMYDS +rumple/SDG +rumply/TR +rumpus/SM +run/AS +runabout/SM +runaround/S +runaway/S +rundown/SM +rune/MS +rung/MS +runic +runlet/SM +runnable +runnel/SM +runner/MS +running/S +runny/RT +runoff/MS +runt/MS +runtime +runtiness/M +runty/RPT +runway/MS +rupee/MS +rupiah/M +rupiahs +rupture/GMSD +rural/Y +rurality/M +ruse/MS +rush/DSRGZ +rusher/M +rushes/I +rushing/M +rushy/RT +rusk/MS +russet/MDS +russetting +rust/MSDG +rustic/S +rustically +rusticate/GSD +rustication/M +rusticity/S +rustiness/MS +rustle/RSDGZ +rustler/M +rustproof/DGS +rusty/XNRTP +rut/MS +rutabaga/SM +ruthenium/MS +rutherfordium/SM +ruthless/YP +ruthlessness/MS +rutted +rutting +rutty/RT +rye/MS +s's/KI +s/XJBG +sabbath +sabbatical/S +saber/GSMD +sabered/U +sable/GMDS +sabot/MS +sabotage/DSMG +saboteur/SM +sabra/MS +sac/SM +saccharides +saccharin/MS +saccharine +sacerdotal +sachem/MS +sachet/SM +sack/GJDRMS +sackcloth/M +sackcloths +sacker/M +sackful/MS +sacking/M +sacra/L +sacral +sacrament/DMGS +sacramental/S +sacred/PY +sacredness/S +sacrifice/RSDZMG +sacrificer/M +sacrificial/Y +sacrilege/MS +sacrilegious/Y +sacristan/SM +sacristy/MS +sacroiliac/S +sacrosanct/P +sacrosanctness/MS +sacrum/M +sad/PY +sadden/DSG +sadder +saddest +saddle's +saddle/UGDS +saddlebag/SM +saddler/M +sades +sadism/MS +sadist/MS +sadistic +sadistically +sadness/SM +sadomasochism/MS +sadomasochist/S +sadomasochistic +safari/GMDS +safe/URPTY +safeguard/MDSG +safekeeping/MS +safeness's/U +safeness/MS +safes +safety/SDMG +safflower/SM +saffron/MS +sag/TSR +saga/MS +sagacious/YP +sagaciousness/M +sagacity/MS +sage/MYPS +sagebrush/SM +sagged +sagger +sagging +saggy/RT +sago/MS +saguaro/SM +sahib/MS +said/U +saids +sail/GJMDRS +sailboard/DGS +sailboat/SRMZG +sailcloth/M +sailcloths +sailer/M +sailfish/SM +sailing/M +sailor/YMS +sailplane/SDMG +saint/YDMGS +sainthood/MS +saintlike +saintliness/MS +saintly/RTP +saith +saiths +sake/MRS +saker/M +saki's +salaam/GMDS +salable/U +salacious/YP +salaciousness/MS +salacity/MS +salad/SM +salamander/MS +salami/MS +salary/SDMG +sale/ABMS +saleability/M +salesclerk/SM +salesgirl/SM +saleslady/S +salesman/M +salesmanship/SM +salesmen +salespeople/M +salesperson/MS +salesroom/M +saleswoman +saleswomen +salience/MS +saliency +salient/SY +saline/S +salinger +salinity/MS +saliva/MS +salivary +salivate/XNGSD +salivation/M +sallow/TGRDSP +sallowness/MS +sally/GSDM +salmon/SM +salmonella/M +salmonellae +salon/SM +saloon/MS +saloonkeeper +salsa/MS +salsify/M +salt/GZTPMDRS +saltcellar/SM +salted/UC +salter/M +saltine/MS +saltiness/SM +saltness/M +saltpeter/SM +salts/C +saltshaker/S +saltwater +salty/RSPT +salubrious/YP +salubriousness/M +salubrity/M +salutariness/M +salutary/P +salutation/SM +salutatory/S +salute/RSDG +saluter/M +salvage/MGRSD +salvageable +salvager/M +salvation/MS +salve/GZMDSR +salver/M +salvo/GMDS +samarium/MS +samba/GSDM +same/SP +sameness/MS +samovar/SM +sampan/MS +sample/RSDJGMZ +sampler/M +sampling/M +samurai/M +sanatorium/MS +sanctification/M +sanctifier/M +sanctify/RSDGNX +sanctimonious/PY +sanctimoniousness/MS +sanctimony/MS +sanction/SMDG +sanctioned/U +sanctity/SM +sanctuary/MS +sanctum/SM +sand/SMDRGZ +sandal/MDGS +sandalwood/SM +sandbag/MS +sandbagged +sandbagging +sandbank/SM +sandbar/S +sandblast/GZSMRD +sandblaster/M +sandbox/MS +sandcastle/S +sander/M +sandhill +sandhog/SM +sandiness/S +sandlot/SM +sandlotter/S +sandman/M +sandmen +sandpaper/DMGS +sandpile +sandpiper/MS +sandpit/M +sandstone/MS +sandstorm/SM +sandwich/SDMG +sandy/PRT +sane/IRYTP +saned +saneness's/I +saneness/MS +sanes +sang/S +sangfroid/S +sangria/SM +sanguinary +sanguine/F +sanguined +sanguinely +sanguineness/M +sanguineous/F +sanguines +sanguining +saning +sanitarian/S +sanitarium/SM +sanitary/S +sanitate/NX +sanitation/M +sanitize/RSDZG +sanitizer/M +sanity/SIM +sank +sans +sanserif +sap/MS +sapience/MS +sapient +sapless +sapling/SM +sapped +sapper/SM +sapphire/MS +sappiness/SM +sapping +sappy/RPT +saprophyte/MS +saprophytic +sapsucker/SM +sapwood/SM +saran/SM +sarape's +sarcasm/MS +sarcastic +sarcastically +sarcoma/MS +sarcophagi +sarcophagus/M +sardine/SDMG +sardonic +sardonically +sarge/SM +sari/MS +sarong/MS +sarsaparilla/MS +sartorial/Y +sartorius/M +sash/GMDS +sashay/GDS +sass/GDSM +sassafras/MS +sassy/TRS +sat/DG +satanic +satanical/Y +satanism/S +satanist/S +satchel/SM +sate/S +sateen/MS +satellite/GMSD +satiable/I +satiate/GNXSD +satiation/M +satiety/MS +satin/MDSG +satinwood/MS +satiny +satire/SM +satiric +satirical/Y +satirist/SM +satirize/DSG +satirizes/U +satisfaction/ESM +satisfactorily/U +satisfactoriness/MU +satisfactory/UP +satisfiability/U +satisfiable/U +satisfied/UE +satisfier/M +satisfies/E +satisfy/GZDRS +satisfying/EU +satisfyingly +satori/SM +satrap/SM +saturate/XDRSNG +saturated/CUA +saturater/M +saturates/A +saturation/M +saturnalia +saturnine/Y +satyr/MS +satyriases +satyriasis/M +satyric +sauce/DSRGZM +saucepan/SM +saucer/M +saucily +sauciness/S +saucy/TRP +sauerkraut/SM +sauna/DMSG +saunter/DRSG +saurian/S +sauropod/SM +sausage/MS +sauté/DGS +savage/GTZYPRSD +savageness/SM +savagery/MS +savanna/MS +savant/SM +save/ZGJDRSB +saved/U +saveloy/M +saver/M +savior/SM +savor/SMRDGZ +savored/U +savorer/M +savorier +savoriest +savoriness/S +savoring/Y +savoringly/S +savory/UMPS +savoy/SM +savvy/GTRSD +saw/SMDRG +sawbones/M +sawbuck/SM +sawdust/MDSG +sawer/M +sawfly/SM +sawhorse/MS +sawmill/SM +sawtooth +sawyer/MS +sax/MS +saxifrage/SM +saxophone/MS +saxophonist/SM +say/USG +sayer/SM +sayest +saying/MS +says/M +scab/SM +scabbard/SGDM +scabbed +scabbiness/SM +scabbing +scabby/RTP +scabies/M +scabrous/YP +scabrousness/M +scad/SM +scaffold/JGDMS +scaffolding/M +scalability +scalar/SM +scalawag/SM +scald/GJRDS +scale/JGZMBDSR +scaled/AU +scaleless +scalene +scaler/M +scales/A +scaliness/MS +scaling/A +scallion/MS +scallop/GSMDR +scalloper/M +scalloping/M +scalp/GZRDMS +scalpel/SM +scalper/M +scalping/M +scaly/TPR +scam/SM +scammed +scamming +scamp/RDMGZS +scamper/GD +scampi/M +scan/AS +scandal/GMDS +scandalize/GDS +scandalized/U +scandalmonger/SM +scandalous/YP +scandalousness/M +scandium/MS +scanned/A +scanner/SM +scanning/A +scansion/SM +scant/CDRSG +scantest +scantily +scantiness/MS +scantly +scantness/MS +scanty/TPRS +scape/M +scapegoat/SGDM +scapegrace/MS +scapula/M +scapulae +scapular/S +scar/DRMSG +scarab/SM +scarce/RTYP +scarceness/SM +scarcity/MS +scare/S +scarecrow/MS +scaremonger/SGM +scaremongering/M +scarer/M +scarf/SDGM +scarface +scarification/M +scarify/DRSNGX +scarily +scariness/S +scarlatina/MS +scarlet/MDSG +scarp/SDMG +scarred +scarring +scarves/M +scary/PTR +scat/S +scathe/DG +scathed/U +scathing/Y +scatological +scatology/SM +scatted +scatter/DRJZSG +scatterbrain/MDS +scatterer/M +scattergun +scattering/YM +scatting +scavenge/GDRSZ +scavenger/M +scenario/SM +scenarist/MS +scene/GMDS +scenery/SM +scenic/S +scenically +scent's/C +scent/GDMS +scented/U +scentless +scents/C +scepter/DMSG +scepters/U +sceptically +sch +schedule's +schedule/ADSRG +scheduled/U +scheduler/MS +schema/M +schemata +schematic/S +schematically +scheme/JSRDGMZ +schemer/M +schemta +scherzo/MS +schilling/SM +schism/SM +schismatic/S +schist/SM +schizo/S +schizoid/S +schizomycetes +schizophrenia/SM +schizophrenic/S +schizophrenically +schlemiel/MS +schlep/S +schlepped +schlepping +schlock/SM +schlocky/TR +schmaltz/MS +schmaltzy/TR +schmo/M +schmoes +schmooze/GSD +schmuck/MS +schnapps/M +schnauzer/MS +schnitzel/MS +schnook/SM +schnoz/S +schnozzle/MS +scholar/SYM +scholarship/MS +scholastic/S +scholastically +school/ZGMRDJS +schoolbag/SM +schoolbook/SM +schoolboy/MS +schoolchild/M +schoolchildren +schooldays +schooled/U +schoolfellow/S +schoolfriend +schoolgirl/MS +schoolgirlish +schoolhouse/MS +schooling/M +schoolmarm/MS +schoolmarmish +schoolmaster/SGDM +schoolmate/MS +schoolmistress/MS +schoolroom/SM +schoolteacher/MS +schoolwork/SM +schoolyard/SM +schooner/SM +schuss/SDMG +schussboomer/S +schwa/SM +sci +sciatic/S +sciatica/SM +science/FMS +scientific/U +scientifically/U +scientist/SM +scimitar/SM +scintilla/MS +scintillate/GNDSX +scintillation/M +scintillator/SM +scion/SM +scissor/SGD +scleroses +sclerosis/M +sclerotic/S +scoff/RDGZS +scoffer/M +scofflaw/MS +scold/GSJRD +scolder/M +scolioses +scoliosis/M +scollop's +sconce/SDGM +scone/SM +scoop/SRDMG +scooper/M +scoot/SRDGZ +scooter/M +scope/DSGM +scops +scorbutic +scorch/ZGRSD +scorcher/M +scorching/Y +score/ZMDSRJG +scoreboard/MS +scorecard/MS +scored/M +scorekeeper/SM +scoreless +scoreline +scorn/SGZMRD +scorner/M +scornful/PY +scornfulness/M +scorpion/SM +scotch/MSDG +scotchs +scoundrel/YMS +scour/SRDGZ +scourer/M +scourge/MGRSD +scourger/M +scouring/M +scout/SRDMJG +scouter/M +scouting/M +scoutmaster/SM +scow/DMGS +scowl/SRDG +scowler/M +scrabble/DRSZG +scrabbler/M +scrag/SM +scragged +scragging +scraggly/TR +scraggy/TR +scram/S +scramble/UDSRG +scrambler's/U +scrambler/MS +scrammed +scramming +scrap/SGZJRDM +scrapbook/SM +scrape/S +scraper/M +scrapheap/SM +scrapped +scrapper/SM +scrapping +scrappy/RT +scrapyard/S +scratch/JDRSZG +scratched/U +scratcher/M +scratches/M +scratchily +scratchiness/S +scratchy/TRP +scrawl/GRDS +scrawler/M +scrawly/RT +scrawniness/MS +scrawny/TRP +scream/ZGSRD +screamer/M +screaming/Y +scree/DSM +screech/GMDRS +screecher/M +screechy/TR +screed/MS +screen/RDMJSG +screened/U +screening/M +screenplay/MS +screenwriter/MS +screw's +screw/GUSD +screwball/SM +screwdriver/SM +screwer/M +screwiness/S +screwup +screwworm/MS +screwy/RTP +scribal +scribble/JZDRSG +scribbler/M +scribe's +scribe/CDRSGIK +scriber/MKIC +scrim/SM +scrimmage/RSDMG +scrimmager/M +scrimp/DGS +scrimshaw/GSDM +scrip/SM +script/FGMDS +scripted/U +scriptural/Y +scripture/MS +scriptwriter/SM +scriptwriting/M +scriven/ZR +scrivener/M +scrod/M +scrofula/MS +scrofulous +scroll/GMDSB +scrollbar/SM +scrooge/SDMG +scrota +scrotal +scrotum/M +scrounge/ZGDRS +scroungy/TR +scrub/S +scrubbed +scrubber/MS +scrubbing +scrubby/TR +scruff/SM +scruffily +scruffiness/S +scruffy/PRT +scrum/MS +scrummage/MG +scrumptious/Y +scrunch/DSG +scrunchy/S +scruple/SDMG +scrupulosity/SM +scrupulous/UPY +scrupulousness's +scrupulousness/US +scrutable/I +scrutinize/RSDGZ +scrutinized/U +scrutinizer/M +scrutinizing/UY +scrutinizingly/S +scrutiny/MS +scuba/SDMG +scud/S +scudded +scudding +scuff/GSD +scuffle/SDG +scull/SRDMGZ +sculler/M +scullery/MS +scullion/MS +sculpt/SDG +sculptor/MS +sculptress/MS +sculptural/Y +sculpture/SDGM +scum/MS +scumbag/S +scummed +scumming +scummy/TR +scupper/SDMG +scurf/MS +scurfy/TR +scurrility/MS +scurrilous/PY +scurrilousness/MS +scurry/GJSD +scurvily +scurviness/M +scurvy/SRTP +scutcheon/SM +scuttle/MGSD +scuttlebutt/MS +scuzzy/RT +scythe/SDGM +sea/MYS +seabed/S +seabird/S +seaboard/MS +seaborne +seacoast/MS +seafare/JRZG +seafarer/M +seafood/MS +seafront/MS +seagoing +seagull/S +seahorse/S +seal/MDRSGZ +sealant/MS +sealed/AU +sealer/M +seals/UA +sealskin/SM +seam/MNDRGS +seamail +seaman/YM +seamanship/SM +seamer/M +seaminess/M +seamless/PY +seamlessness/M +seams/I +seamstress/MS +seamy/TRP +seaplane/SM +seaport/SM +seaquake/M +sear/DRSJGT +search/RSDAGZ +searcher/AM +searching/YS +searchlight/SM +searing/Y +seascape/SM +seashell/MS +seashore/SM +seasick/P +seasickness/SM +seaside/SM +season/JRDYMBZSG +seasonable/UP +seasonableness/M +seasonably/U +seasonal/Y +seasonality +seasoned/U +seasoner/M +seasoning/M +seat's +seat/UDSG +seatbelt +seated/A +seater/M +seating/SM +seawall/S +seaward/S +seawater/S +seaway/MS +seaweed/SM +seaworthiness/MU +seaworthinesses +seaworthy/TRP +sebaceous +seborrhea/SM +sec'y +sec/S +secant/SM +secede/GRSD +secession/MS +secessionist/MS +seclude/GSD +secluded/YP +secludedness/M +seclusion/SM +seclusive +second/RDYZGSL +secondarily +secondary/PS +seconder/M +secondhand +secrecy/MS +secret/TVGRDYS +secretarial +secretariat/MS +secretary/SM +secretaryship/MS +secrete/XNS +secretion/M +secretive/PY +secretiveness/S +secretory +sect/ISM +sectarian/S +sectarianism/MS +sectary/MS +section/ASEM +sectional/SY +sectionalism/MS +sectionalized +sectioned +sectioning +sector/EMS +sectoral +sectored +sectoring +sects/E +secular/SY +secularism/MS +secularist/MS +secularity/M +secularization/MS +secularize/GSD +secularized/U +secure/PGTYRSDJ +secured/U +securely/I +security/MSI +secy +sedan/SM +sedate/PXVNGTYRSD +sedateness/SM +sedation/M +sedative/S +sedentary +sedge/SM +sedgy/RT +sediment/SGDM +sedimentary +sedimentation/SM +sedition/SM +seditious/PY +seditiousness/M +seduce/RSDGZ +seducer/M +seduction/MS +seductive/YP +seductiveness/MS +seductress/SM +sedulous/Y +see/U +seed's +seed/ADSG +seedbed/MS +seedcase/SM +seeded/U +seeder/MS +seediness/MS +seeding/S +seedless +seedling/SM +seedpod/S +seedy/TPR +seeing's +seeing/U +seeings +seek/GZSR +seeker/M +seeking/Y +seem/GJSYD +seeming/Y +seemliness's +seemliness/US +seemly/UTPR +seen/U +seep/GSD +seepage/MS +seer/SM +seersucker/MS +sees +seesaw/DMSG +seethe/SDGJ +segment/SGDM +segmental/Y +segmentation/SM +segmented/U +segregant +segregate/XCNGSD +segregated/U +segregation/CM +segregationist/SM +segregative +segue/DS +segueing +seigneur/MS +seignior/SM +seine/GZMDSR +seiner/M +seismic +seismically +seismograph/ZMR +seismographer/M +seismographic +seismographs +seismography/SM +seismologic +seismological +seismologist/MS +seismology/SM +seismometer/S +seize/BJGZDSR +seizer/M +seizin/MS +seizing/M +seizor/MS +seizure/MS +seldom +select/PDSVGB +selected/UAC +selection/MS +selectional +selective/YP +selectiveness/M +selectivity/MS +selectman/M +selectmen +selectness/SM +selector/SM +selects/A +selenate/M +selenite/M +selenium/MS +selenographer/SM +selenography/MS +self/GPDMS +selfish/PUY +selfishness/SU +selfless/YP +selflessness/MS +selfness/M +selfsame/P +selfsameness/M +sell/AZGSR +seller/AM +sellout/MS +seltzer/S +selvage/MGSD +selves/M +semantic/S +semantical/Y +semanticist/SM +semantics/M +semaphore/GMSD +semblance/ASME +semen/SM +semester/SM +semi/SM +semiannual/Y +semiarid +semiautomated +semiautomatic/S +semicircle/SM +semicircular +semicolon/MS +semiconductor/SM +semiconscious +semidefinite +semidetached +semidrying/M +semifinal/MS +semifinalist/MS +semilogarithmic +semimonthly/S +seminal/Y +seminar/SM +seminarian/MS +seminary/MS +semiofficial +semiotic/S +semioticians +semiotics/M +semipermanent/Y +semipermeable +semiprecious +semiprivate +semiprofessional/YS +semipublic +semiquantitative/Y +semiretired +semisecret +semiskilled +semisolid/S +semistructured +semisweet +semitic/S +semitone/SM +semitrailer/SM +semitrance +semitransparent +semitropical +semivowel/MS +semiweekly/S +semiyearly +semolina/SM +sempiternal +sempstress/SM +sen +senate/MS +senator/MS +senatorial +send/SRGZ +sender/M +sends/A +senescence/SM +senescent +senile/SY +senility/MS +senior/MS +seniority/SM +senna/MS +senor/MS +senora/S +senorita/S +sens/DSG +sensate/YNX +sensately/I +sensation/M +sensational/Y +sensationalism/MS +sensationalist/S +sensationalize/GSD +sense/M +senseless/PY +senselessness/SM +sensibility/ISM +sensible/PRST +sensibleness/MS +sensibly/I +sensitive/YIP +sensitiveness's/I +sensitiveness/MS +sensitives +sensitivity/ISM +sensitization/CSM +sensitize/SDCG +sensitized/U +sensitizers +sensor/MS +sensory +sensual/YF +sensualist/MS +sensuality/MS +sensuous/PY +sensuousness/S +sent/UFEA +sentence/SDMG +sentential/Y +sententious/Y +sentience/ISM +sentient/YS +sentiment/MS +sentimental/Y +sentimentalism/SM +sentimentalist/SM +sentimentality/SM +sentimentalization/SM +sentimentalize/RSDZG +sentimentalizes/U +sentinel/GDMS +sentry/SM +sepal/SM +separability/MSI +separable/PI +separableness/MI +separably/I +separate/YNGVDSXP +separateness/MS +separates/M +separation/M +separatism/SM +separatist/SM +separator/SM +sepia/MS +sepses +sepsis/M +sept/M +septa/M +septate/N +septennial/Y +septet/MS +septic/S +septicemia/SM +septicemic +septillion/M +septuagenarian/MS +septum/M +sepulcher/MGSD +sepulchers/UA +sepulchral/Y +seq +sequel/MS +sequence's/F +sequence/DRSJZMG +sequenced/A +sequencer/M +sequences/F +sequent/F +sequential/YF +sequentiality/FM +sequentialize/DSG +sequester/SDG +sequestrate/XGNDS +sequestration/M +sequin/SDMG +sequitur +sequoia/MS +sera's +seraglio/SM +serape/S +seraph/M +seraphic +seraphically +seraphim's +seraphs +sere/TGDRS +serenade/MGDRS +serenader/M +serendipitous/Y +serendipity/MS +serene/GTYRSDP +sereneness/SM +serenity/MS +serf/MS +serfdom/MS +serge/DSGM +sergeant/SM +serial/MYS +serialization/MS +serialize/GSD +series/M +serif/SMD +serigraph/M +serigraphs +serious/PY +seriousness/SM +sermon/SGDM +sermonize/GSD +serological/Y +serology/MS +serons +serous +serpent/GSDM +serpentine/GYS +serrate/GNXSD +serration/M +serried +serum/MS +servant/SDMG +serve/AGCFDSR +served/U +server/MCF +servers +service's/E +service/MGSRD +serviceability/SM +serviceable/P +serviceableness/M +serviced/U +serviceman/M +servicemen +services/E +servicewoman +servicewomen +serviette/MS +servile/U +servilely +servileness/M +serviles +servility/SM +serving/SM +servitor/SM +servitude/MS +servo/S +servomechanism/MS +servomotor/MS +sesame/MS +sesquicentennial/S +sessile +session/SM +set's +set/SIA +setback/S +setscrew/SM +sett/BJGZSMR +settable/A +settee/MS +setter/M +setting's +setting/AS +settle/AUDSG +settlement/ASM +settler/MS +settling/S +setup/MS +seven/SMH +sevenfold +sevenpence +seventeen/HMS +seventeenths +sevenths +seventieths +seventy/MSH +sever/SGTRD +several/YS +severalfold +severalty/M +severance/SM +severe/PY +severed/E +severeness/SM +severing/E +severity/MS +severs/E +sew/SAGD +sewage/MS +sewer/GSMD +sewerage/SM +sewing/SM +sewn +sex/GMDS +sexagenarian/MS +sexily +sexiness/MS +sexism/SM +sexist/SM +sexless +sexologist/SM +sexology/MS +sexpot/SM +sextant/SM +sextet/SM +sextillion/M +sexton/MS +sextuple/MDG +sextuplet/MS +sexual/Y +sexuality/MS +sexualized +sexy/RTP +sf +sh/DRS +shabbily +shabbiness/SM +shabby/RTP +shack/GMDS +shackle's +shackle/UGDS +shackler/M +shad/DRJGSM +shade/SM +shaded/U +shadeless +shadily +shadiness/MS +shading/M +shadow/GSDRM +shadowbox/SDG +shadower/M +shadowiness/M +shadowy/TRP +shady/TRP +shaft/SDMG +shafting/M +shag/MS +shagged +shagginess/SM +shagging +shaggy/TPR +shah/M +shahs +shakable/U +shakably/U +shake/SRGZB +shakeable +shakedown/S +shaken/U +shakeout/SM +shaker/M +shakeup/S +shakily +shakiness/S +shaking/M +shaky/TPR +shale/SM +shall +shallot/SM +shallow/STPGDRY +shallowness/SM +shalom +shalt +sham/MDSG +shaman/SM +shamanic +shamble/DSG +shambles/M +shame/SM +shamefaced/Y +shameful/YP +shamefulness/S +shameless/PY +shamelessness/SM +shammed +shammer +shamming +shammy's +shampoo/DRSMZG +shampooer/M +shamrock/SM +shan't +shandy/M +shanghai/SDG +shank/SMDG +shantis +shantung/MS +shanty/SM +shantytown/SM +shape's +shape/AGDSR +shaped/U +shapeless/PY +shapelessness/SM +shapeliness/S +shapely/RPT +shaper/S +sharable/U +shard/SM +share/DSRGZMB +shareable +sharecrop/S +sharecropped +sharecropper/MS +sharecropping +shared/U +shareholder/MS +shareholding/S +sharer/M +shareware/S +sharia/SM +shark/SGMD +sharkskin/SM +sharp/SGTZXPYRDN +sharpen/ASGD +sharpened/U +sharpener/S +sharper/M +sharpie/SM +sharpness/MS +sharpshoot/JRGZ +sharpshooter/M +sharpshooting/M +sharpy's +shat +shatter/DSG +shattering/Y +shatterproof +shave/DSRJGZ +shaved/U +shaver/M +shaving/M +shaw/M +shawl/SDMG +shay/MS +she'd +she'll +she/M +sheaf/MDGS +shear/RDGZS +shearer/M +sheath/GJMDRS +sheathe/UGSD +sheather/M +sheathing/M +sheaths +sheave/SDG +sheaves/M +shebang/MS +shed's +shed/U +shedding +sheds +sheen/MDGS +sheeny/TRSM +sheep/M +sheepdog/SM +sheepfold/MS +sheepherder/MS +sheepish/YP +sheepishness/SM +sheepskin/SM +sheer/PGTYRDS +sheerness/S +sheet/RDMJSG +sheeting/M +sheetlike +sheik/SM +sheikdom/SM +sheikh's +shekel/MS +shelf/MDGS +shell/RDMGS +shellac/S +shellacked +shellacking/MS +shelled/U +shellfire/SM +shellfish/SM +shelter/DRMGS +sheltered/U +shelterer/M +shelve/JRSDG +shelver/M +shelves/M +shelving/M +shenanigan/SM +shepherd/DMSG +shepherdess/S +sherbet/MS +sherd's +sheriff/SM +sherlock/M +sherry/MS +shew/GSD +shewn +shh +shiatsu/S +shibboleth/M +shibboleths +shield/MDRSG +shielded/U +shielder/M +shift/RDGZS +shiftily +shiftiness/SM +shiftless/PY +shiftlessness/S +shifty/TRP +shill/DJSG +shillelagh/M +shillelaghs +shilling/M +shim/SM +shimmed +shimmer/DGS +shimmery +shimming +shimmy/DSMG +shin/SGZDRM +shinbone/SM +shindig/MS +shine/S +shiner/M +shingle/MDRSG +shingler/M +shinguard +shininess/MS +shining/Y +shinned +shinning +shinny/GDSM +shinsplints +shiny/PRT +ship's +ship/SLA +shipboard/MS +shipborne +shipbuild/RGZJ +shipbuilder/M +shipload/SM +shipman/M +shipmate/SM +shipmen +shipment/AMS +shipowner/MS +shippable +shipped/A +shipper/SM +shipping/MS +shipshape +shipwreck/GSMD +shipwright/MS +shipyard/MS +shire/MS +shirk/RDGZS +shirker/M +shirr/GJDS +shirt/JDMSG +shirtfront/S +shirting/M +shirtless +shirtmake/R +shirtmaker/M +shirtsleeve/MS +shirttail/S +shirtwaist/SM +shit/S! +shitting/! +shitty/RT! +shiv/SZRM +shiver/GDR +shiverer/M +shivery +shivved +shivving +shlemiel's +shoal/SRDMGT +shoat/SM +shock/SGZRD +shocker/M +shocking/Y +shockproof +shod/U +shoddily +shoddiness/SM +shoddy/RSTP +shoe/MS +shoehorn/GSMD +shoeing +shoelace/MS +shoemake/RZ +shoemaker/M +shoer's +shoeshine/MS +shoestring/MS +shoetree/MS +shogun/MS +shogunate/SM +shone +shoo/DSG +shoofly +shook/SM +shoot/SJRGZ +shooter/M +shootout/MS +shop/MS +shopkeep/RGZ +shopkeeper/M +shoplift/SRDGZ +shoplifter/M +shoplifting/M +shoppe/RSDGZJ +shopped/M +shopper/M +shopping/M +shoptalk/SM +shopworn +shore/DSRGMJ +shorebird/S +shoreline/SM +shoring/M +short/SGTXYRDNP +shortage/MS +shortbread/MS +shortcake/SM +shortchange/DSG +shortcoming/MS +shortcrust +shortcut/MS +shortcutting +shorten/RDGJ +shortener/M +shortening/M +shortfall/SM +shorthand/DMS +shorthorn/MS +shortie's +shortish +shortlist/GD +shortness/MS +shortsighted/YP +shortsightedness/S +shortstop/MS +shortwave/SM +shorty/SM +shot/MS +shotgun/SM +shotgunned +shotgunner +shotgunning +shotted +shotting +should/TZR +shoulder/GMD +shouldn't +shout/SGZRDM +shove/DSRG +shovel/MDRSZG +shoveler/M +shovelful/MS +shover/M +show/GDRZJS +showbiz +showbizzes +showboat/SGDM +showcase/MGSD +showdown/MS +shower/GDM +showery/TR +showgirl/SM +showily +showiness/MS +showing/M +showman/M +showmanship/SM +showmen +shown +showoff/S +showpiece/SM +showplace/SM +showroom/MS +showy/RTP +shpt +shrank +shrapnel/SM +shred/MS +shredded +shredder/MS +shredding +shrew/GSMD +shrewd/RYTP +shrewdness/SM +shrewish/PY +shrewishness/M +shriek/SGDRMZ +shrieker/M +shrift/SM +shrike/SM +shrill/DRTGPS +shrillness/MS +shrilly +shrimp/MDGS +shrine/SDGM +shrink/SRBG +shrinkage/SM +shrinker/M +shrinking/U +shrive/RSDG +shrivel/GSD +shriven +shroud/GSMD +shrub/SM +shrubbed +shrubbery/SM +shrubbing +shrubby/TR +shrug/S +shrugged +shrugging +shrunk/N +shtick/S +shuck/SGMRD +shucker/M +shucks/S +shudder/DSG +shuddery +shuffle/GDSRZ +shuffleboard/MS +shuffled/A +shuffles/A +shuffling/A +shun/S +shunned +shunning +shunt/GSRD +shunter/M +shush/SDG +shut/S +shutdown/MS +shuteye/SM +shutoff/M +shutout/SM +shutter/DMGS +shutterbug/S +shuttering/M +shutting +shuttle/MGDS +shuttlecock/MDSG +shy/DRSGTZY +shyer +shyest +shyness/SM +shyster/SM +sibilance/M +sibilancy/M +sibilant/SY +sibling/SM +sibyl/SM +sibylline +sic/S +sick/GXTYNDRSP +sickbay/M +sickbed/S +sicken/JRDG +sickener/M +sickening/Y +sicker/Y +sickie/SM +sickish/PY +sickle/SDGM +sickliness/M +sickly/TRSDPG +sickness/MS +sicko/S +sickout/S +sickroom/SM +side/ISRM +sidearm/S +sideband/MS +sidebar/MS +sideboard/SM +sideburns +sidecar/MS +sided/A +sidedness +sidekick/MS +sidelight/SM +sideline/MGDRS +sidelong +sideman/M +sidemen +sidepiece/S +sider/FA +sidereal +sides/A +sidesaddle/MS +sideshow/MS +sidesplitting +sidestep/S +sidestepped +sidestepping +sidestroke/GMSD +sideswipe/GSDM +sidetrack/SDG +sidewalk/MS +sidewall/MS +sidewards +sideway/SM +sidewinder/SM +siding/SM +sidle/DSG +siege/GMDS +sienna/SM +sierra/SM +siesta/MS +sieve/GZMDS +sift/GZJSDR +sifted/UA +sifter/M +sigh/DRG +sigher/M +sighs +sight/ISM +sighted/P +sighter/M +sighting/S +sightless/Y +sightliness/UM +sightly/TURP +sightread +sightsee/RZ +sightseeing/S +sigma/SM +sigmoid +sign's +sign/GARDCS +signal's +signal/A +signaled +signaler/S +signaling +signalization/S +signalize/GSD +signally +signalman/M +signalmen +signals +signatory/SM +signature/MS +signboard/MS +signed/FU +signer/SC +signet/SGMD +significance/IMS +significant/YS +significantly/I +signification/M +signify/DRSGNX +signing/S +signor/SFM +signora/SM +signore/M +signori +signories +signorina/SM +signorine +signpost/DMSG +signs/F +silage/GMSD +siled +silence/MZGRSD +silencer/M +silent/TSPRY +silentness/M +silhouette/GMSD +silica/SM +silicate/SM +siliceous +silicide/M +silicon/MS +silicone/SM +silicoses +silicosis/M +silk/GXNDMS +silken/DG +silkily +silkiness/SM +silkscreen/SM +silkworm/MS +silky/RSPT +sill/MS +silliness/SM +silly/PRST +silo/GSM +silt/MDGS +siltation/M +siltstone/M +silty/RT +silver/RDYMGS +silverer/M +silverfish/MS +silversmith/M +silversmiths +silverware/SM +silvery/RTP +simian/S +similar/EY +similarity/EMS +simile/SM +similitude/SME +simmer/GSD +simonize/SDG +simony/MS +simpatico +simper/GDS +simple/RSDGTP +simpleminded/YP +simpleness/S +simpleton/SM +simplex/S +simplicity/MS +simplified/U +simplify/ZXRSDNG +simplistic +simplistically +simply +simulacrum/M +simulate/XENGSD +simulation/ME +simulative +simulator/SEM +simulcast/GSD +simultaneity/SM +simultaneous/YP +simultaneousness/M +sin/MAGS +since +sincere/IY +sincereness/M +sincerer +sincerest +sincerity/MIS +sine/SM +sinecure/MS +sinecurist/M +sinew/SGMD +sinewy +sinful/YP +sinfulness/SM +sing/BGJZYDR +singe/S +singeing +singer/M +singing/Y +single/PSDG +singlehanded/Y +singleness/SM +singlet/SM +singleton/SM +singletree/SM +singsong/GSMD +singular/SY +singularity/SM +singularization/M +sinister/YP +sinisterness/M +sinistral/Y +sink/GZSDRB +sinkable/U +sinker/M +sinkhole/SM +sinking/M +sinless/YP +sinlessness/M +sinned +sinner/MS +sinning +sinter/DM +sinuosity/MS +sinuous/PY +sinuousities +sinuousness/M +sinus/MS +sinusitis/SM +sinusoid/MS +sinusoidal/Y +sip/S +siphon/DMSG +siphons/U +sipped +sipper/SM +sipping +sir/XGMNDS +sire/MS +sired/C +siren/M +sires/C +siring/C +sirloin/MS +sirocco/MS +sirred +sirring +sirup's +sis/S +sisal/MS +sissified +sissy/TRSM +sister's/A +sister/GDYMS +sisterhood/MS +sisterliness/MS +sisterly/P +sit/AG +sitar/SM +sitarist/SM +sitcom/SM +site/DSJM +sits +sitter/MS +sitting/SM +situ/S +situate/GNSDX +situation/M +situational/Y +situationist +situs/M +six/MRSH +sixfold +sixgun +sixpence/MS +sixpenny +sixshooter +sixteen/HRSM +sixteenths +sixth/Y +sixths +sixtieths +sixty/SMH +sizable/P +sizableness/M +size/GJDRSBMZ +sized/UA +sizer/M +sizes/A +sizing/M +sizzle/RSDG +sizzler/M +ska/S +skat/JMDRGZ +skate/SM +skateboard/SJGZMDR +skater/M +skedaddle/GSD +skeet/RMS +skein/MDGS +skeletal/Y +skeleton/MS +skeptic/SM +skeptical/Y +skepticism/MS +sketch/MRSDZG +sketchbook/SM +sketcher/M +sketchily +sketchiness/MS +sketchpad +sketchy/PRT +skew/DRSPGZ +skewer/GDM +skewing/M +skewness/M +ski/MNJSG +skid/S +skidded +skidding +skiff/GMDS +skiing/M +skilfully +skill/DMSG +skilled/U +skillet/MS +skillful/YUP +skillfulness/MU +skillfulnesses +skilling/M +skim/SM +skimmed +skimmer/MS +skimming/SM +skimp/GDS +skimpily +skimpiness/MS +skimpy/PRT +skin/SM +skincare +skindive/G +skinflint/MS +skinhead/SM +skinless +skinned +skinner/SM +skinniness/MS +skinning +skinny/TRSP +skintight +skip/S +skipped +skipper/SGDM +skipping +skirmish/RSDMZG +skirmisher/M +skirt/RDMGS +skirter/M +skirting/M +skit/GSMD +skitter/SDG +skittish/YP +skittishness/SM +skittle/SM +skivvy/GSDM +skoal/SDG +skulduggery/MS +skulk/SRDGZ +skulker/M +skull/SDM +skullcap/MS +skullduggery's +skunk/GMDS +sky/MDRSGZ +skycap/MS +skydiver/SM +skydiving/MS +skyhook +skyjack/ZSGRDJ +skyjacker/M +skylark/SRDMG +skylarker/M +skylight/MS +skyline/MS +skyrocket/GDMS +skyscrape/RZ +skyscraper/M +skyward/S +skywave +skyway/M +skywriter/MS +skywriting/MS +slab/MS +slabbed +slabbing +slack/SPGTZXYRDN +slacken/DG +slacker/M +slackness/MS +slag/MS +slagged +slagging +slain +slake/DSG +slaked/U +slalom/SGMD +slam/S +slammed +slammer/S +slamming +slander/MDRZSG +slanderous/PY +slanderousness/M +slang/SMGD +slangy/TR +slant/SDG +slanting/Y +slantwise +slap/MS +slapdash/S +slaphappy/TR +slapped +slapper +slapping +slapstick/MS +slash/GZRSD +slashing/Y +slat/MDRSGZ +slate/SM +slater/M +slather/SMDG +slating/M +slatted +slattern/MYS +slatting +slaughter/SJMRDGZ +slaughterer/M +slaughterhouse/SM +slave/DSRGZM +slaveholder/SM +slaver/GDM +slavery/SM +slavish/YP +slavishness/SM +slaw/MS +slay/RGZS +sleaze/S +sleazily +sleaziness/SM +sleazy/RTP +sled/SM +sledded +sledder/S +sledding +sledge/SDGM +sledgehammer/MDGS +sleek/PYRDGTS +sleekness/S +sleep/RMGZS +sleeper/M +sleepily +sleepiness/SM +sleeping/M +sleepless/YP +sleeplessness/SM +sleepover/S +sleepwalk/JGRDZS +sleepwalker/M +sleepwear/M +sleepy/PTR +sleepyhead/MS +sleet/DMSG +sleety/TR +sleeve/SDGM +sleeveless +sleeving/M +sleigh/GMD +sleighs +sleight/SM +sleken/DG +slender/RYTP +slenderize/DSG +slenderness/MS +slept +sleuth/GMD +sleuths +slew/DGS +slice/DSRGZM +sliced/U +slicer/M +slick/PSYRDGTZ +slicker/M +slickness/MS +slid/GZDR +slide/S +slider/M +slight/DRYPSTG +slighter/M +slighting/Y +slightness/S +slim/SPGYD +slime/SM +sliminess/S +slimline +slimmed +slimmer/S +slimmest +slimming/S +slimness/S +slimy/PTR +sling/GMRS +slings/U +slingshot/MS +slink/GS +slinky/RT +slip/SM +slipcase/MS +slipcover/GMDS +slipknot/SM +slippage/SM +slipped +slipper/GSMD +slipperiness/S +slippery/PRT +slipping +slipshod +slipstream/MDGS +slipway/SM +slit/SM +slither/DSG +slithery +slitted +slitter/S +slitting +sliver/GSDM +slivery +slob/MS +slobber/SDG +slobbery +sloe/MS +slog/S +slogan/MS +sloganeer/MG +slogged +slogging +sloop/SM +slop/DRSGZ +slope/S +sloped/U +slopped +sloppily +sloppiness/SM +slopping +sloppy/RTP +slosh/GSDM +slot/MS +sloth/GDM +slothful/PY +slothfulness/MS +sloths +slotted +slotting +slouch/DRSZG +sloucher/M +slouchy/RT +slough/GMD +sloughs +sloven/YMS +slovenliness/SM +slovenly/TRP +slow/PGTYDRS +slowcoaches +slowdown/MS +slowish +slowness/MS +slowpoke/MS +sludge/SDGM +sludgy/TR +slue/MGDS +slug/MS +sluggard/MS +slugged +slugger/SM +slugging +sluggish/YP +sluggishness/SM +sluice/SDGM +slum/MS +slumber/MDRGS +slumberer/M +slumberous +slumlord/MS +slummed +slummer +slumming +slummy/TR +slump/DSG +slung/U +slunk +slur/MS +slurp/GSD +slurred +slurried/M +slurring +slurry/MGDS +slurrying/M +slush/SDMG +slushiness/SM +slushy/RTP +slut/MS +sluttish +slutty/TR +sly/RTY +slyness/MS +smack/SMRDGZ +smacker/M +small/SGTRDP +smallholders +smallholding/MS +smallish +smallness/S +smallpox/SM +smalltalk +smalltime +smarmy/RT +smart/YRDNSGTXP +smarten/GD +smartness/S +smartypants +smash/GZRSD +smasher/M +smashing/Y +smashup/S +smattering/SM +smear/GRDS +smearer/M +smeary/TR +smell/SBRDG +smeller/M +smelliness/MS +smelly/TRP +smelt/SRDGZ +smelter/M +smidgen/MS +smilax/MS +smile/GMDSR +smiley/M +smilies +smiling/UY +smirch/SDG +smirk/GSMD +smite/GSR +smiter/M +smith/DMG +smithereens +smiths +smithy/SM +smitten +smock/SGMDJ +smocking/M +smog/SM +smoggy/TR +smoke/GZMDSRBJ +smokehouse/MS +smokeless +smoker/M +smokescreen/S +smokestack/MS +smokiness/S +smoking/M +smoky/RSPT +smolder/SGD +smoldering/Y +smooch/SDG +smooth/TZGPRDNY +smoothen/DG +smoother/M +smoothie/SM +smoothness/MS +smooths +smote +smother/GSD +smudge/GSD +smudginess/M +smudgy/TRP +smug/YSP +smugged +smugger +smuggest +smugging +smuggle/JZGSRD +smuggler/M +smugness/MS +smut/SM +smutted +smuttiness/SM +smutting +smutty/TRP +smörgåsbord/SM +snack/SGMD +snaffle/GDSM +snafu/DMSG +snag/MS +snagged +snagging +snail/GSDM +snake/DSGM +snakebird/M +snakebite/MS +snakelike +snakeroot/M +snaky/TR +snap/US +snapback/M +snapdragon/MS +snapped/U +snapper/SM +snappily +snappiness/SM +snapping/U +snappish/PY +snappishness/SM +snappy/PTR +snapshot/MS +snapshotted +snapshotting +snare/DSRGM +snarer/M +snarf/JSGD +snarl/UGSD +snarler/M +snarling/Y +snarly/RT +snatch/DRSZG +snatcher/M +snazzily +snazzy/TR +sneak/RDGZS +sneaker/MD +sneakily +sneakiness/SM +sneaking/Y +sneaky/PRT +sneer/GMRDJS +sneerer/M +sneering/Y +sneeze/SRDG +snick/MRZ +snicker/GMRD +snide/YTSRP +snideness/M +sniff/GZSRD +sniffer/M +sniffle/GDRS +sniffler/M +sniffles/M +snifter/MDSG +snigger's +snip/SGDRZ +snipe/SM +sniper/M +snipped +snipper/SM +snippet/SM +snipping +snippy/RT +snit/SM +snitch/GDS +snivel/JSZGDR +sniveler/M +snob/MS +snobbery/SM +snobbish/YP +snobbishness/S +snobby/RT +snood/SGDM +snook/SMRZ +snooker/GMD +snoop/SRDGZ +snooper/M +snoopy/RT +snoot/SDMG +snootily +snootiness/MS +snooty/TRP +snooze/GSD +snore/DSRGZ +snorkel/ZGSRDM +snort/GSZRD +snorter/M +snot/MS +snotted +snottily +snottiness/SM +snotting +snotty/TRP +snout/SGDM +snow/GDMS +snowball/SDMG +snowbank/SM +snowbird/SM +snowblower/S +snowboard/GZDRJS +snowbound +snowcapped +snowdrift/MS +snowdrop/MS +snowfall/MS +snowfield/MS +snowflake/MS +snowily +snowiness/MS +snowman/M +snowmen +snowmobile/GMDRS +snowplough/M +snowploughs +snowplow/SMGD +snowshed +snowshoe/MRS +snowshoeing +snowshoer/M +snowstorm/MS +snowsuit/S +snowy/RTP +snub/SP +snubbed +snubber +snubbing +snuff/GZSYRD +snuffbox/SM +snuffer/M +snuffle/GDSR +snuffler/M +snuffly/RT +snug/SYP +snugged +snugger +snuggest +snugging +snuggle/GDS +snuggly +snugness/MS +so +soak/GDRSJ +soaker/M +soap/MDRGS +soapbox/DSMG +soapiness/S +soapstone/MS +soapsud/S +soapy/RPT +soar/DRJSG +soarer/M +soaring/Y +sob/SZR +sobbed +sobbing/Y +sober/PGTYRD +soberer/M +soberness/SM +sobriety/SIM +sobriquet/MS +soc/S +soccer/MS +sociabilities +sociability/IM +sociable/S +sociably/IU +social/SY +socialism/SM +socialist/SM +socialistic +socialite/SM +sociality/M +socialization/SM +socialize/RSDG +socialized/U +socializer/M +socially/U +societal/Y +society/MS +socio +sociobiology/M +sociocultural/Y +sociodemographic +socioeconomic/S +socioeconomically +sociolinguistics/M +sociological/MY +sociologist/SM +sociology/SM +sociometric +sociometry/M +sociopath/M +sociopaths +sock/GDMS +socket/SMDG +sod/MS +soda/SM +sodded +sodden/DYPSG +soddenness/M +sodding +sodium/MS +sodomite/MS +sodomize/GDS +sodomy/SM +soever +sofa/SM +soft/SPXTYNR +softball/MS +softbound +soften/ZGRD +softener/M +softhearted +softie's +softness/MS +software/MS +softwood/SM +softy/SM +soggily +sogginess/S +soggy/RPT +soigné +soil/SGMD +soiled/U +soirée/SM +sojourn/RDZGSM +sol/GSMDR +solace/GMSRD +solacer/M +solar/S +solaria +solarium/M +sold/RU +solder/RDMSZG +soldier/MDYSG +soldiery/MS +sole/YSP +solecism/MS +soled/FA +solemn/PTRY +solemness +solemnify/GSD +solemnity/MS +solemnization/SM +solemnize/GSD +solemnness/SM +solenoid/MS +soler/F +soles/IFA +solicit/SDG +solicitation/S +solicited/U +solicitor/MS +solicitous/YP +solicitousness/S +solicitude/MS +solid/STYRP +solidarity/MS +solidi +solidification/M +solidify/NXSDG +solidity/S +solidness/SM +solidus/M +soliloquies +soliloquize/DSG +soliloquy/M +soling/NM +solipsism/MS +solipsist/S +solitaire/SM +solitary/SP +solitude/SM +solo/DMSG +soloist/SM +solstice/SM +solubility/IMS +soluble/SI +solute's +solute/ENAXS +solution/AME +solvable/UI +solvating +solve/ABSRDZG +solved/EU +solvency/IMS +solvent's +solvent/IS +solvently +solver/MEA +solves/E +solving/E +soma/M +somatic +somber/PY +somberness/SM +sombre +sombrero/SM +some/Z +somebody'll +somebody/SM +someday +somehow +someone'll +someone/SM +someplace/M +somersault/DSGM +somerset/S +somersetted +somersetting +something/S +sometime/S +someway/S +somewhat/S +somewhere/S +sommelier/SM +somnambulism/SM +somnambulist/SM +somnolence/MS +somnolent/Y +son/SMY +sonar/SM +sonata/MS +sonatina/SM +song/MS +songbag +songbird/SM +songbook/S +songfest/MS +songful/YP +songfulness/M +songster/MS +songstress/SM +songwriter/SM +songwriting +sonic/S +sonnet/MDSG +sonny/SM +sonority/S +sonorous/PY +sonorousness/SM +sonuvabitch +soon/TR +soonish +soot/MGDS +sooth/GZTYSRDMJ +soothe +soother/M +soothing/YP +soothingness/M +sooths +soothsay/JGZR +soothsayer/M +sooty/RT +sop/SM +sophism/SM +sophist/RMS +sophister/M +sophistic/S +sophistical +sophisticate/XNGDS +sophisticated/U +sophisticatedly +sophistication/MU +sophistry/SM +sophomore/SM +sophomoric +soporific/SM +soporifically +sopped +sopping/S +soppy/RT +soprano/SM +sorbet/SM +sorcerer/MS +sorceress/S +sorcery/MS +sordid/PY +sordidness/SM +sore/PYTGDRS +sorehead/SM +soreness/S +sorghum/MS +sorority/MS +sorrel/SM +sorrily +sorriness/SM +sorrow/GRDMS +sorrower/M +sorrowful/YP +sorrowfulness/SM +sorry/PTSR +sort's +sort/FSAGD +sorta +sortable +sorted/U +sorter/MS +sortie/MSD +sortieing +sos +sot/SM +sottish +sou'wester +sou/SMH +soubriquet's +soufflé/MS +sough/DG +soughs +sought/U +soul/MDS +soulful/YP +soulfulness/MS +soulless/Y +sound's +sound/AUD +soundboard/MS +sounder's +sounder/U +sounders +soundest +sounding's +sounding/AY +soundings +soundless/Y +soundly/U +soundness/UMS +soundproof/GSD +soundproofing/M +sounds/A +soundtrack/MS +soup/GMDS +soupy/RT +soupçon/SM +sour/TYDRPSG +source/ASDMG +sourceless +sourdough +sourdoughs +sourish +sourness/MS +sourpuss/MS +sous/DSG +sousaphone/SM +souse +south/RDMG +southbound +southeast/RZMS +southeaster/YM +southeastern +southeastward/S +souther/MY +southerly/S +southern/PZSYR +southerner/M +southernisms +southernmost +southing/M +southland/M +southpaw/MS +souths +southward/S +southwest/RMSZ +southwester/YM +southwestern +southwestward/S +souvenir/SM +sovereign/YMS +sovereignty/MS +soviet/MS +sow/ADGS +sowbelly/M +sowens/M +sower/DS +sown/A +sox's +soy/MS +soybean/MS +spa/MS +space/DSRGZMJ +spacecraft/MS +spaceflight/S +spaceman/M +spacemen +spaceport/SM +spacer/M +spaceship/MS +spacesuit/MS +spacewalk/GSMD +spacewoman +spacewomen +spacey +spacial +spacier +spaciest +spaciness +spacing/M +spacious/PY +spaciousness/SM +spade/DSRGM +spadeful/SM +spader/M +spadework/SM +spadices +spadix/M +spaghetti/SM +spake +span/MS +spandex/MS +spandrels +spangle/GMDS +spaniel/SM +spanielled +spanielling +spank/SRDJG +spanker/M +spanking/M +spanned/U +spanner/SM +spanning +spar/DRMGTS +spare/PSY +spareness/MS +sparer/M +spareribs +sparing/UY +spark/SGMRD +sparker/M +sparkle/DRSGZ +sparkler/M +sparky/RT +sparling/SM +sparred +sparrer +sparring/U +sparrow/MS +spars/TR +sparse/YP +sparseness/S +sparsity/S +spartan +spasm/GSDM +spasmodic +spasmodically +spastic/S +spat/MS +spate/SM +spathe/MS +spatial/Y +spatiality/M +spatted +spatter/DGS +spatterdock/M +spatting +spatula/SM +spavin/DMS +spawn/MRDSG +spawner/M +spay/DGS +speak/RBGZJS +speakable/U +speakeasy/SM +speaker/M +speakership/M +speaking/U +spear/MRDGS +spearer/M +spearfish/SDMG +spearhead/GSDM +spearmint/MS +spec'd +spec'ing +spec/SM +special/SRYP +specialism/MS +specialist/MS +specialization/SM +specialize/GZDSR +specialized/U +specializing/U +specialty/MS +specie/MS +specif +specifiability +specifiable +specifiably +specific/SP +specifically +specification/SM +specificity/S +specified/U +specifier/SM +specifies +specify/AD +specifying +specimen/SM +specious/YP +speciousness/SM +speck/GMDS +speckle/GMDS +spectacle/MSD +spectacular/SY +spectator/SM +specter's/A +specter/DMS +spectra/M +spectral/YP +spectralness/M +spectrogram/MS +spectrograph/M +spectrographically +spectrography/M +spectrometer/MS +spectrometric +spectrometry/M +spectrophotometer/SM +spectrophotometric +spectrophotometry/M +spectroscope/SM +spectroscopic +spectroscopically +spectroscopy/SM +spectrum/M +specular/Y +specularity +speculate/VNGSDX +speculation/M +speculative/Y +speculator/SM +sped +speech/GMDS +speechless/YP +speechlessness/SM +speed/RMJGZS +speedboat/GSRM +speedboating/M +speeder/M +speedily +speediness/SM +speedometer/MS +speedster/SM +speedup/MS +speedway/SM +speedwell/MS +speedy/PTR +speer/M +speleological +speleologist/S +speleology/MS +spell/RDSJGZ +spellbind/SRGZ +spellbinder/M +spellbound +spelldown/MS +spelled/A +speller/M +spelling/M +spells/A +spelunker/MS +spelunking/S +spend/SBJRGZ +spender/M +spendthrift/MS +spent/U +sperm/SM +spermatophyte/M +spermatozoa +spermatozoon/M +spermicidal +spermicide/MS +spew/DRGZJS +spewer/M +sphagnum/SM +sphere/SDGM +spheric/S +spherical/Y +spherics/M +spheroid/SM +spheroidal/Y +spherule/MS +sphincter/SM +sphinx/MS +spic/DGM +spice/SM +spicebush/M +spicily +spiciness/SM +spicule/MS +spicy/PTR +spider/SM +spiderweb/S +spiderwort/M +spidery/TR +spiel/GDMS +spier/M +spiffy/TDRSG +spigot/MS +spike/GMDSR +spiker/M +spikiness/SM +spiky/PTR +spill/RDSG +spillage/SM +spillover/SM +spillway/SM +spin/S +spinach/MS +spinal/YS +spindle/JGMDRS +spindly/RT +spine/MS +spineless/YP +spinelessness/M +spinet/SM +spininess/M +spinnability/M +spinnaker/SM +spinner/SM +spinneret/MS +spinning/SM +spinster/MS +spinsterhood/SM +spinsterish +spiny/PRT +spiracle/SM +spiraea's +spiral/YDSG +spire's +spire/AIDSGF +spirea/MS +spirit/GMDS +spirited/PY +spiritedness/M +spiritless +spirits/I +spiritual/SYP +spiritualism/SM +spiritualist/SM +spiritualistic +spirituality/SM +spirituous +spirochete/SM +spiry/TR +spit/SGD +spitball/SM +spite's/A +spite/CSDAG +spiteful/PY +spitefuller +spitefullest +spitefulness/MS +spitfire/SM +spitted +spitting +spittle/SM +spittoon/SM +splash/GZDRS +splashdown/MS +splasher/M +splashily +splashiness/MS +splashy/RTP +splat/SM +splatted +splatter/DSG +splatting +splay/SDG +splayfeet +splayfoot/MD +spleen/SM +splendid/YRPT +splendidness/M +splendor/SM +splendorous +splenetic/S +splice/RSDGZJ +splicer/M +spline/MSD +splint/SGZMDR +splinter/GMD +splintery +split/SM +splits/M +splittable +splitter/MS +splitting/S +splodge/SM +splotch/MSDG +splotchy/RT +splurge/GMDS +splutter/RDSG +splutterer/M +spoil/CSZGDR +spoilables +spoilage/SM +spoiled/U +spoiler/MC +spoilsport/SM +spoke/DSG +spoken/U +spokeshave/MS +spokesman/M +spokesmen +spokespeople +spokesperson/S +spokeswoman/M +spokeswomen +spoliation/MCS +sponge/GMZRSD +spongecake +sponger/M +sponginess/S +spongy/TRP +sponsor/DGMS +sponsorship/S +spontaneity/SM +spontaneous/PY +spontaneousness/M +spoof/SMDG +spook/SMDG +spookiness/MS +spooky/PRT +spool/SRDMGZ +spoon/GSMD +spoonbill/SM +spoonerism/SM +spoonful/MS +spoor/GSMD +sporadic/Y +sporadically +spore/DSGM +sporran/MS +sport/VGSRDM +sportiness/SM +sporting/Y +sportive/PY +sportiveness/M +sportscast/RSGZM +sportsman/MY +sportsmanlike/U +sportsmanship/MS +sportsmen +sportswear/M +sportswoman/M +sportswomen +sportswriter/S +sporty/PRT +spot/MSC +spotless/YP +spotlessness/MS +spotlight/GDMS +spotlit +spotted/U +spotter/MS +spottily +spottiness/SM +spotting/M +spotty/RTP +spousal/MS +spouse/GMSD +spout/SGRD +spouter/M +sprain/SGD +sprang/S +sprat/SM +sprawl/GSD +spray/GZSRDM +sprayed/UA +sprayer/M +sprays/A +spread/RSJGZB +spreadeagled +spreader/M +spreadsheet/S +spree/MDS +spreeing +sprig/MS +sprigged +sprigging +sprightliness/MS +sprightly/PRT +spring/SGZR +springboard/MS +springbok/MS +springeing +springer/M +springily +springiness/SM +springing/M +springlike +springtime/MS +springy/TRP +sprinkle/DRSJZG +sprinkler/DM +sprinkling/M +sprint/SGZMDR +sprite/SM +spritz/GZDSR +sprocket/DMGS +sprocketed/U +sprout/GSD +spruce/GMTYRSDP +spruceness/SM +sprue/M +sprung/U +spry/TRY +spryness/S +spud/MS +spudded +spudding +spume/DSGM +spumone's +spumoni/S +spumy/TR +spun +spunk/GSMD +spunky/SRT +spur/MS +spurge/MS +spurious/PY +spuriousness/SM +spurn/RDSG +spurred +spurring +spurt/SGD +sputa +sputnik/MS +sputter/DRGS +sputum/M +spy/DRSGM +spyglass/MS +sq +sqq +sqrt +squab/SM +squabbed +squabber +squabbest +squabbing +squabble/ZGDRS +squabbler/M +squad/SM +squadded +squadding +squadron/MDGS +squalid/PRYT +squalidness/SM +squall/GMRDS +squaller/M +squally/RT +squalor/SM +squamous/Y +squander/GSRD +square/GMTYRSDP +squareness/SM +squarer/M +squarish +squash/GSRD +squashiness/M +squashy/RTP +squat/SPY +squatness/MS +squatted +squatter/SMDG +squattest +squatting +squaw/SM +squawk/GRDMZS +squawker/M +squeak/RDMGZS +squeaker/M +squeakily +squeakiness/S +squeaky/RPT +squeal/MRDSGZ +squealer/M +squeamish/YP +squeamishness/SM +squeegee/DSM +squeegeeing +squeeze/GZSRDB +squeezer/M +squelch/GDRS +squelcher/M +squelchy/RT +squib/SM +squibbed +squibbing +squid/SM +squidded +squidding +squiggle/MGDS +squiggly/RT +squint/GTSRD +squinter/M +squinting/Y +squire/SDGM +squirehood +squirm/SGD +squirmy/TR +squirrel/SGYDM +squirt/GSRD +squirter/M +squish/GSD +squishy/RTP +ssh +st/GBJ +stab/YS +stabbed +stabber/S +stabbing/S +stability/ISM +stabilizability +stabilization's +stabilization/CS +stabilize/CGSD +stabilizer/MS +stable's/F +stable/RSDGMTP +stableman/M +stablemate +stablemen +stableness/UM +stabler/U +stables/F +stablest/U +stabling/M +stably/U +staccato/S +stack's +stack/USDG +stackable +stacker/M +stadia's +stadias +stadium/MS +staff's +staff/ADSG +staffer/MS +staffroom +stag/DRMJSGZ +stage/SM +stagecoach/MS +stagecraft/MS +stagehand/MS +stager/M +stagestruck +stagflation/SM +stagged +stagger/GSJDR +staggerer/M +staggering/Y +staggers/M +stagging +staginess/M +staging/M +stagnancy/SM +stagnant/Y +stagnate/NGDSX +stagnation/M +stagy/PTR +staid/YRTP +staidness/MS +stain/SGRD +stained/U +stainer/M +stainless/YS +stair/MS +staircase/SM +stairway/SM +stairwell/MS +stake/DSGM +stakeholder/S +stakeout/SM +stalactite/SM +stalag/M +stalagmite/SM +stale/PGYTDSR +stalemate/SDMG +staleness/MS +stalk/MRDSGZJ +stalker/M +stall/DMSJG +stalled/I +stallholders +stallion/SM +stalls/I +stalwart/PYS +stalwartness/M +stamen/MS +stamina/SM +staminate +stammer/DRSZG +stammerer/M +stammering/Y +stamp/RDSGZJ +stamped/U +stampede/MGDRS +stampeder/M +stamper/M +stance/MIS +stanch/GDRST +stancher/M +stanchion/SGMD +stand/SJGZR +standalone +standard/YMS +standardization/AMS +standardize/GZDSR +standardized/U +standardizer/M +standardizes/A +standby +standbys +standee/MS +standing/M +standoff/SM +standoffish +standout/MS +standpipe/MS +standpoint/SM +standstill/SM +stank/S +stannic +stannous +stanza/MS +staph/M +staphs +staphylococcal +staphylococci +staphylococcus/M +staple/ZRSDGM +stapled/U +stapler/M +star/DRMGZS +starboard/SDMG +starch/MDSG +starchily +starchiness/MS +starchy/TRP +stardom/MS +stardust/MS +stare/S +starfish/SM +stargaze/ZGDRS +staring/U +stark/SPGTYRD +starkness/MS +starless +starlet/MS +starlight/MS +starling/MS +starlit +starred +starring +starry/TR +starship +starstruck +start/ASGDR +starter/MS +startle/GDS +startling/PY +startup/SM +starvation/MS +starve/RSDG +starveling/M +starver/M +stash/GSD +stasis/M +stat/DRSGV +state's/K +state/IGASD +statecraft/MS +stated/U +statehood/MS +statehouse/S +stateless/P +statelessness/MS +stateliness/MS +stately/PRT +statement/MSA +stater/M +stateroom/SM +states/K +stateside +statesman/MY +statesmanlike +statesmanship/SM +statesmen +stateswoman +stateswomen +statewide +static/S +statical/Y +statics/M +station/SZGMDR +stationarity +stationary/S +stationer/M +stationery/MS +stationmaster/M +statistic/MS +statistical/Y +statistician/MS +stator/SM +statuary/SM +statue/MSD +statuesque/YP +statuette/MS +stature/MS +status/SM +statute/SM +statutorily +statutory/P +staunch/PDRSYTG +staunchness/S +stave/DGM +stay/DRGZS +stayer/M +std +stdio +stead/SGDM +steadfast/PY +steadfastness/MS +steadily/U +steadiness's +steadiness/US +steading/M +steady/DRSUTGP +steak/SM +steakhouse/SM +steal/SRHG +stealer/M +stealing/M +stealth/M +stealthily +stealthiness/MS +stealths +stealthy/PTR +steam/SGZRDMJ +steamboat/MS +steamer/MDG +steamfitter/S +steamfitting/S +steamily +steaminess/SM +steamroll/GZRDS +steamroller/DMG +steamship/SM +steamy/RSTP +steed/SM +steel/SDMGZ +steeliness/SM +steelmaker/M +steelwork/ZSMR +steelworker/M +steely/TPRS +steelyard/MS +steep/SYRNDPGTX +steepen/GD +steeper/M +steeple/MS +steeplebush/M +steeplechase/GMSD +steeplejack/MS +steepness/S +steer/SGBRDJ +steerage/MS +steerer/M +steersman/M +steersmen +steeves +stegosauri +stegosaurus/S +stein/SGZMRD +stellar +stellated +stem/MS +stemless +stemmed/U +stemming +stemware/MS +stench/GMDS +stencil/GDRMSZ +stenciler/M +stencillings +steno/SM +stenographer/SM +stenographic +stenography/SM +stenotype/M +stentorian +step/MIS +stepbrother/MS +stepchild/M +stepchildren +stepdaughter/MS +stepfather/SM +stepladder/SM +stepmother/SM +stepparent/SM +steppe/RSDGMZ +stepper/M +steppingstone/S +stepsister/SM +stepson/SM +stepwise +stereo/GSDM +stereographic +stereography/M +stereophonic +stereoscope/MS +stereoscopic +stereoscopically +stereoscopy/M +stereotype/GMZDRS +stereotypic +stereotypical/Y +sterile +sterility/SM +sterilization/SM +sterilize/RSDGZ +sterilized/U +sterilizes/A +sterling/MPYS +sterlingness/M +stern/SYRDPGT +sternal +sternness/S +sternum/SM +steroid/MS +steroidal +stertorous +stet/MS +stethoscope/SM +stetson/MS +stetted +stetting +stevedore/GMSD +stew/GDMS +steward/DMSG +stewardess/SM +stewardship/MS +stick/MRDSGZ +sticker/M +stickily +stickiness/SM +stickle/GZDR +stickleback/MS +stickler/M +stickpin/SM +stickup/SM +sticky/GPTDRS +stiff/GTXPSYRND +stiffen/JZRDG +stiffness/MS +stifle/GJRSD +stifler/M +stifling/Y +stigma/MS +stigmata +stigmatic/S +stigmatization's +stigmatization/C +stigmatizations +stigmatize/DSG +stigmatized/U +stile/GMDS +stiletto/MDSG +still/RDIGS +stillbirth/M +stillbirths +stillborn/S +stiller/MI +stillest +stillness/MS +stilt/GDMS +stilted/PY +stimulant/MS +stimulate/SDVGNX +stimulated/U +stimulation/M +stimulative/S +stimulator/M +stimulatory +stimuli/M +stimulus/MS +sting/GZR +stinger/M +stingily +stinginess/MS +stinging/Y +stingray/MS +stingy/RTP +stink/GZRJS +stinkbug/S +stinker/M +stinking/Y +stinkpot/M +stinky/RT +stint/JGRDMS +stinter/M +stinting/U +stipend/MS +stipendiary +stipple/JDRSG +stippler/M +stipulate/XNGSD +stipulation/M +stir/S +stirred/U +stirrer/SM +stirring/YS +stirrup/SM +stitch's +stitch/ASDG +stitcher/M +stitchery/S +stitching/MS +stoat/SM +stochastic +stochastically +stochasticity +stock's +stock/SGAD +stockade/SDMG +stockbreeder/SM +stockbroker/MS +stockbroking/S +stocker/SM +stockholder/SM +stockily +stockiness/SM +stockinet's +stockinette/S +stocking/MDS +stockist/MS +stockpile/GRSD +stockpiler/M +stockpot/MS +stockroom/MS +stocktaking/MS +stocky/PRT +stockyard/SM +stodge/M +stodgily +stodginess/S +stodgy/TRP +stogy/SM +stoic/MS +stoical/Y +stoichiometric +stoichiometry/M +stoicism/SM +stoke/DSRGZ +stoker/M +stokes/M +stole/MDS +stolen +stolid/PTYR +stolidity/S +stolidness/S +stolon/SM +stomach/RSDMZG +stomachache/MS +stomacher/M +stomachs +stomp/DSG +stone/DSRGM +stonecutter/SM +stoneless +stonemason/MS +stoner/M +stonewall/GDS +stoneware/MS +stonewashed +stonework/SM +stonewort/M +stonily +stoniness/MS +stony/TPR +stood +stooge/SDGM +stool/SDMG +stoop/SDG +stop's +stop/US +stopcock/MS +stopgap/SM +stoplight/SM +stopover/MS +stoppable/U +stoppage/MS +stopped/U +stopper/GMDS +stopping/M +stopple/GDSM +stops/M +stopwatch/SM +storage/SM +store's +store/ADSRG +storefront/SM +storehouse/MS +storekeep/ZR +storekeeper/M +storeroom/SM +stork/SM +storm/SRDMGZ +stormbound +stormer/M +stormily +storminess/S +stormtroopers +stormy/PTR +story/GSDM +storyboard/MDSG +storybook/MS +storyline +storyteller/SM +storytelling/MS +stoup/SM +stout/STYRNP +stouten/DG +stouthearted +stoutness/MS +stove/DSRGM +stovepipe/SM +stover/M +stow/GDS +stowage/SM +stowaway/MS +straddle/ZDRSG +straddler/M +strafe/GRSD +strafer/M +straggle/GDRSZ +straggly/RT +straight/RNDYSTXGP +straightaway/S +straightedge/MS +straighten/ZGDR +straightener/M +straightforward/SYP +straightforwardness/MS +straightjacket's +straightness/MS +straightway/S +strain/ASGZDR +strained/UF +strainer/MA +straining/F +strains/F +strait/XTPSMGYDNR +straiten/DG +straitjacket/GDMS +straitlaced +straitness/M +strand/SDRG +stranded/P +strange/PYZTR +strangeness/SM +stranger/GMD +strangle/JDRSZG +stranglehold/MS +strangles/M +strangulate/NGSDX +strangulation/M +strap's +strap/US +strapless/S +strapped/U +strapping/S +strata/MS +stratagem/SM +strategic/S +strategical/Y +strategics/M +strategist/SM +strategy/SM +strati +stratification/M +stratified/U +stratify/NSDGX +stratigraphic +stratigraphical +stratigraphy/M +stratosphere/SM +stratospheric +stratospherically +stratum/M +stratus/M +straw/SMDG +strawberry/SM +strawflower/SM +stray/GSRDM +strayer/M +streak/DRMSGZ +streaker/M +streaky/TR +stream/GZSMDR +streamed/U +streamer/M +streaming/M +streamline/SRDGM +street/SMZ +streetcar/MS +streetlight/SM +streetwalker/MS +streetwise +strength/NMX +strengthen/AGDS +strengthener/MS +strengths +strenuous/PY +strenuousness/SM +strep/MS +streptococcal +streptococci +streptococcus/M +streptomycin/SM +stress/DSMG +stressed/U +stressful/YP +stretch/BDRSZG +stretchability/M +stretchable/U +stretcher/DMG +stretchy/TRP +strew/GDHS +strewn +stria/M +striae +striate/DSXGN +striated/U +striation/M +stricken +strict/AF +stricter +strictest +strictly +strictness/S +stricture/SM +stridden +stride/RSGM +stridency/S +strident/Y +strider/M +strife/SM +strike/RSGZJ +strikebreak/ZGR +strikebreaker/M +strikebreaking/M +strikeout/S +striker/M +striking/Y +string's +string/SAG +stringed +stringency/S +stringent/Y +stringer/MS +stringiness/SM +stringing/M +stringy/RTP +strip/GRDMS +stripe/SM +striper/M +stripling/M +stripped/U +stripper/MS +stripping +striptease/SRDGZM +stripteaser/M +stripy/RT +strive/JRSG +striven +striver/M +strobe/SDGM +stroboscope/SM +stroboscopic +strode +stroke/ZRSDGM +stroking/M +stroll/GZSDR +stroller/M +strong/YRT +strongbow +strongbox/MS +stronghold/SM +strongish +strongman/M +strongmen +strongroom/MS +strontium/SM +strop/SM +strophe/MS +strophic +stropped +stropping +strove +struck +structural/Y +structuralism/M +structuralist/SM +structure/SRDMG +structured/AU +structureless +structures/A +structuring/A +strudel/MS +struggle/GDRS +struggler/M +strum/S +strummed +strumming +strumpet/GSDM +strung/UA +strut/S +strutted +strutter/M +strutting +strychnine/MS +stub/MS +stubbed/M +stubbing +stubble/SM +stubbly/RT +stubborn/SGTYRDP +stubbornness/SM +stubby/SRT +stucco/GDM +stuccoes +stuck/U +stud/MS +studbook/SM +studded +studding/SM +student/SM +studentship/MS +studied/PY +studiedness/M +studier/SM +studio/MS +studious/PY +studiousness/SM +study/AGDS +stuff/JGSRD +stuffily +stuffiness/SM +stuffing/M +stuffy/TRP +stultify/NXGSD +stumble/GZDSR +stumbling/Y +stump/RDMSG +stumpage/M +stumper/M +stumpy/RT +stun/S +stung +stunk +stunned +stunner/M +stunning/Y +stunt/GSDM +stunted/P +stupefaction/SM +stupefy/DSG +stupendous/PY +stupendousness/M +stupid/PTYRS +stupidity/SM +stupidness/M +stupor/MS +sturdily +sturdiness/SM +sturdy/SRPT +sturgeon/SM +stutter/DRSZG +sty/DSGM +style/GZMDSR +styled/A +styles/A +styli +styling/A +stylish/PY +stylishness/S +stylist/MS +stylistic/S +stylistically +stylites +stylization/MS +stylize/DSG +stylos +stylus/SM +stymie/SD +stymieing +stymy's +styptic/S +styrene/MS +suable +suasion/EMS +suave/PRYT +suaveness/S +suavity/SM +sub/MS +subaltern/SM +subarctic/S +subareas +subassembly/M +subatomic/S +subbasement/SM +subbed +subbing +subbranch/S +subcaste/M +subcategorizing +subcategory/SM +subchain +subclass/MS +subclassifications +subclauses +subcommand/S +subcommittee/SM +subcompact/S +subcomponent/MS +subcomputation/MS +subconcept +subconscious/PSY +subconsciousness/SM +subconstituent +subcontinent/MS +subcontinental +subcontract/SMDG +subcontractor/SM +subcultural +subculture/GMDS +subcutaneous/Y +subdirectory/S +subdistrict/M +subdivide/SRDG +subdivision/SM +subdue/GRSD +subdued/Y +subduer/M +subexpression/MS +subfamily/SM +subfield/MS +subfile/SM +subfreezing +subgoal/SM +subgraph +subgraphs +subgroup/SGM +subharmonic/S +subhead/MGJS +subheading/M +subhuman/S +subindex/M +subinterval/MS +subj +subject/GVDMS +subjection/SM +subjective/PSY +subjectiveness/M +subjectivist/S +subjectivity/SM +subjoin/DSG +subjugate/NGXSD +subjugation/M +subjunctive/S +sublayer +sublease/DSMG +sublet/S +subletting +sublimate/GNSDX +sublimation/M +sublime/GRSDTYP +sublimeness/M +sublimer/M +subliminal/Y +sublimity/SM +sublist/SM +subliterary +sublunary +submachine +submarginal +submarine/MZGSRD +submariner/M +submerge/DSG +submergence/SM +submerse/XNGDS +submersible/S +submersion/M +submicroscopic +submission/SAM +submissive/PY +submissiveness/MS +submit/SA +submittable +submittal +submitted/A +submitter/S +submitting/A +submode/S +submodule/MS +subnational +subnet/SM +subnetwork/SM +subnormal/SY +suboptimal +suborbital +suborder/MS +subordinate/YVNGXPSD +subordinately/I +subordinates/I +subordination/IMS +subordinator +suborn/GSD +subornation/SM +subpage +subparagraph/M +subpart/MS +subplot/MS +subpoena/GSDM +subpopulation/MS +subproblem/SM +subprocess/SM +subprofessional/S +subprogram/SM +subproject +subproof/SM +subquestion/MS +subrange/SM +subregion/MS +subregional/Y +subrogation/M +subroutine/SM +subsample/MS +subschema/MS +subscribe/ASDG +subscriber/SM +subscript/SGD +subscripted/U +subscription/MS +subsection/SM +subsegment/SM +subsentence +subsequence/MS +subsequent/SYP +subservience/SM +subservient/SY +subset/MS +subside/SDG +subsidence/MS +subsidiarity +subsidiary/MS +subsidization/MS +subsidize/ZRSDG +subsidized/U +subsidizer/M +subsidy/MS +subsist/SGD +subsistence/MS +subsistent +subsocietal +subsoil/DRMSG +subsonic +subspace/MS +subspecies/M +substance/MS +substandard +substantial/PYS +substantially/IU +substantialness/M +substantiate/VGNSDX +substantiated/U +substantiation/MFS +substantive/PSYM +substantiveness/M +substantivity +substation/MS +substerilization +substitutability +substitute/NGVBXDRS +substituted/U +substitution/M +substitutionary +substitutive/Y +substrata +substrate/MS +substratum/M +substring/S +substructure/SM +subsume/SDG +subsurface/S +subsystem/MS +subtable/S +subtask/SM +subteen/SM +subtenancy/MS +subtenant/SM +subtend/DS +subterfuge/SM +subterranean/SY +subtest +subtext/SM +subtitle/DSMG +subtle/RPT +subtleness/M +subtlety/MS +subtly/U +subtopic/SM +subtotal/GSDM +subtract/SRDZVG +subtracter/M +subtraction/MS +subtrahend/SM +subtree/SM +subtropic/S +subtropical +subtype/MS +subunit/SM +suburb/MS +suburban/S +suburbanite/MS +suburbanization/MS +suburbanized +suburbanizing +suburbia/SM +subvention/MS +subversion/SM +subversive/SPY +subversiveness/MS +subvert/SGDR +subverter/M +subway/MDGS +subzero +succeed/GDRS +succeeder/M +success/MSV +successful/UY +successfulness/M +succession/SM +successive/YP +successiveness/M +successor/MS +successorship +succinct/RYPT +succinctness/SM +succor/SGZRDM +succored/U +succorer/M +succotash/SM +succubus/M +succulence/SM +succulency/MS +succulent/S +succumb/SDG +such +suchlike +suck/GZSDRB +sucker/DMG +suckle/SDJG +suckling/M +sucrose/MS +suction/SMGD +sud/S +sudden/YPS +suddenness/SM +suds/DSRG +sudsy/TR +sue/ZGDRS +sued/DG +suede/SM +suer/M +suet/MS +suety +suffer/SJRDGZ +sufferance/SM +sufferer/M +suffering/M +suffice/GRSD +sufficiency/SIM +sufficient/IY +suffix/GMRSD +suffixation/S +suffixed/U +suffocate/XSDVGN +suffocating/Y +suffragan/S +suffrage/MS +suffragette/MS +suffragist/SM +suffuse/VNGSDX +suffusion/M +sugar/SJGMD +sugarcane/S +sugarcoat/GDS +sugarless +sugarplum/MS +sugary/TR +suggest/DRZGVS +suggester/M +suggestibility/SM +suggestible +suggestion/MS +suggestive/PY +suggestiveness/MS +sugillate +suicidal/Y +suicide/GSDM +suit/MDGZBJS +suitability/SU +suitable/P +suitableness/S +suitably/U +suitcase/MS +suite/SM +suited/U +suiting/M +suitor/SM +sukiyaki/SM +sulfa/S +sulfaquinoxaline +sulfate/MSDG +sulfide/S +sulfite/M +sulfonamide/SM +sulfur/DMSG +sulfuric +sulfurous/YP +sulfurousness/M +sulk/GDS +sulkily +sulkiness/S +sulky/RSPT +sullen/TYRP +sullenness/MS +sullied/U +sully/GSD +sulphate/SM +sulphide/MS +sulphuric +sultan/SM +sultana/SM +sultanate/MS +sultrily +sultriness/SM +sultry/PRT +sum/MRS +sumac/SM +sumach's +sumer/F +summability/M +summable +summand/MS +summarily +summarization/MS +summarize/GSRDZ +summarized/U +summarizer/M +summary/MS +summation/FMS +summed +summer/SGDM +summerhouse/MS +summertime/MS +summery/TR +summing +summit/GMDS +summitry/MS +summon/JSRDGZ +summoner/M +summons/MSDG +sumo/SM +sump/SM +sumptuous/PY +sumptuousness/SM +sun/MS +sunbaked +sunbath/ZRSDG +sunbathe +sunbather/M +sunbathing/M +sunbaths +sunbeam/MS +sunblock/S +sunbonnet/MS +sunburn/GSMD +sunburst/MS +suncream +sundae/MS +sunder/SDG +sundial/MS +sundown/MRDSZG +sundowner/M +sundris +sundry/S +sunfish/SM +sunflower/MS +sung/U +sunglass/MS +sunk/SN +sunlamp/S +sunless +sunlight/MS +sunlit +sunned +sunniness/SM +sunning +sunny/RSTP +sunrise/GMS +sunroof/S +sunscreen/S +sunset/MS +sunsetting +sunshade/MS +sunshine/MS +sunshiny +sunspot/SM +sunstroke/MS +suntan/SM +suntanned +suntanning +sunup/MS +sup/RSZ +super/DG +superabundance/MS +superabundant +superannuate/GNXSD +superannuation/M +superb/YRPT +superbness/M +supercargo/M +supercargoes +supercharge/SRDZG +supercharger/M +supercilious/PY +superciliousness/SM +supercity/S +superclass/M +supercomputer/MS +supercomputing +superconcept +superconducting +superconductivity/SM +superconductor/SM +supercooled +supercooling +supercritical +superdense +superego/SM +supererogation/MS +supererogatory +superficial/SPY +superficiality/S +superfine +superfix/M +superfluity/MS +superfluous/YP +superfluousness/S +superheat/D +superhero/SM +superheroes +superhighway/MS +superhuman/YP +superhumanness/M +superimpose/SDG +superimposition/MS +superintend/GSD +superintendence/S +superintendency/SM +superintendent/SM +superior/SMY +superiority/MS +superlative/PYS +superlativeness/M +superlunary +supermachine +superman/M +supermarket/SM +supermen +supermodel +supermom/S +supernal +supernatant +supernatural/SPY +supernaturalism/M +supernaturalness/M +supernormal/Y +supernova/MS +supernovae +supernumerary/S +superordinate +superpose/BSDG +superposition/MS +superpower/MS +superpredicate +supersaturate/XNGDS +supersaturation/M +superscribe/GSD +superscript/DGS +superscription/SM +supersede/SRDG +superseder/M +supersensitive/P +supersensitiveness/M +superset/MS +supersonic/S +supersonically +supersonics/M +superstar/SM +superstition/SM +superstitious/YP +superstore/S +superstructural +superstructure/SM +supertanker/SM +supertitle/MSDG +superuser/MS +supervene/GSD +supervention/S +supervise/SDGNX +supervised/U +supervision/M +supervisor/SM +supervisory +superwoman/M +superwomen +supine/PSY +supineness/M +supp/YDRGZ +supper/DMG +suppl/RDGT +supplant/SGRD +supplanter/M +supple/SPLY +supplement/SMDRG +supplemental/S +supplementary/S +supplementation/S +supplementer/M +suppleness/SM +suppliant/S +supplicant/MS +supplicate/NGXSD +supplication/M +supplier/AM +supply/MAZGSRD +support/ZGVSBDR +supportability/M +supportable/UI +supported/U +supporter/M +supporting/Y +supportive/Y +suppose/SRDBJG +supposed/Y +supposition/MS +suppository/MS +suppress/VGSD +suppressant/S +suppressed/U +suppressible/I +suppression/SM +suppressive/P +suppressor/S +suppurate/NGXSD +suppuration/M +supra +supranational +supranationalism/M +suprasegmental +supremacist/SM +supremacy/SM +supremal +supreme/PSRTY +supremeness/M +supremo/M +supt +surcease/DSMG +surcharge/MGSD +surcingle/MGSD +surd/M +sure/PU +sured/I +surefire +surefooted +surely +sureness's/U +sureness/MS +surer/I +surest +surety/SM +surf/SJDRGMZ +surface/GSRDPZM +surfaced/UA +surfacer/AMS +surfaces/A +surfacing/A +surfactant/SM +surfboard/MDSG +surfeit/SDRMG +surfer/M +surfing/M +surge/GYMDS +surged/A +surgeon/MS +surgery/MS +surges/A +surgical/Y +surliness/SM +surly/TPR +surmise/SRDG +surmiser/M +surmount/DBSG +surmountable/IU +surname/GSDM +surpass/GDS +surpassed/U +surpassing/Y +surplice/SM +surplus/MS +surplussed +surplussing +surprise/MGDRSJ +surprised/U +surpriser/M +surprising/YU +surreal/S +surrealism/MS +surrealist/S +surrealistic +surrealistically +surreality +surrender/DRSG +surrenderer/M +surreptitious/PY +surreptitiousness/S +surrey/SM +surrogacy/S +surrogate/SDMNG +surrogation/M +surround/JGSD +surrounding/M +surtax/SDGM +surveillance/SM +surveillant +survey/JDSG +surveyed/A +surveying/M +surveyor/MS +surveys/A +survivability/M +survivable/U +survival/MS +survivalist/S +survive/SRDBG +survivor/MS +survivorship/M +susceptibilities +susceptibility/IM +susceptible/I +sushi/SM +suspect/GSDR +suspected/U +suspecter/M +suspecting/U +suspend/DRZGS +suspended/UA +suspender/M +suspense/MXNVS +suspenseful +suspension/AM +suspensive/Y +suspensor/M +suspicion/GSMD +suspicious/YP +suspiciousness/M +sustain/DRGLBS +sustainability +sustainable/U +sustainer/M +sustainment/M +sustenance/MS +sutler/MS +suture/GMSD +suzerain/SM +suzerainty/MS +svelte/RPTY +swab/MS +swabbed +swabbing +swabby/S +swaddle/SDG +swag/GMS +swagged +swagger/GSDR +swagging +swain/SM +swallow/GDRS +swallower/M +swallowtail/SM +swam +swami/SM +swamp/SRDMG +swamper/M +swampland/MS +swampy/RPT +swan/MS +swank/RDSGT +swankily +swankiness/MS +swanky/PTRS +swanlike +swanned +swanning +swap/S +swappable/U +swapped +swapper/SM +swapping +sward/MSGD +swarm/GSRDM +swarmer/M +swart/P +swarthiness/M +swarthy/RTP +swash/GSRD +swashbuckler/SM +swashbuckling/S +swastika/SM +swat/S +swatch/MS +swath/SRDMGJ +swathe +swather/M +swaths +swatted +swatter/MDSG +swatting +sway/DRGS +swayback/SD +swayer/M +swear/SGZR +swearer/M +swearword/SM +sweat/SGZRM +sweatband/MS +sweater/M +sweatily +sweatiness/M +sweatpants +sweatshirt/S +sweatshop/MS +sweaty/TRP +swede/SM +sweep/SBRJGZ +sweeper/M +sweeping/PY +sweepingness/M +sweeps/M +sweepstake's +sweepstakes +sweet/TXSYRNPG +sweetbread/SM +sweetbrier/SM +sweetcorn +sweeten/ZDRGJ +sweetened/U +sweetener/M +sweetening/M +sweetheart/MS +sweetie/MS +sweeting/M +sweetish/Y +sweetmeat/MS +sweetness/MS +sweetshop +swell/SJRDGT +swellhead/DS +swelling/M +swelter/DJGS +sweltering/Y +swept +sweptback +swerve/GSD +swerving/U +swift/GTYRDPS +swifter/M +swiftness/MS +swig/SM +swigged +swigging +swill/SDG +swim/S +swimmer/MS +swimming/MYS +swimsuit/MS +swindle/GZRSD +swindler/M +swine/SM +swineherd/MS +swing/SGRZJB +swingeing +swinger/M +swinging/Y +swingy/R +swinish/PY +swinishness/M +swipe/DSG +swirl/SGRD +swirling/Y +swirly/TR +swish/GSRD +swishy/R +swiss +switch/GBZMRSDJ +switchback/GDMS +switchblade/SM +switchboard/MS +switcher/M +switchgear +switchman/M +switchmen/M +switchover/M +swivel/GMDS +swizzle/RDGM +swob's +swollen +swoon/GSRD +swooning/Y +swoop/RDSG +swoosh/GSD +swop's +sword/DMSG +swordfish/SM +swordplay/RMS +swordplayer/M +swordsman/M +swordsmanship/SM +swordsmen +swordtail/M +swore +sworn +swot/S +swum +swung +sybarite/MS +sybaritic +sycamore/SM +sycophancy/S +sycophant/SYM +sycophantic +sycophantically +syllabi's +syllabic/S +syllabicate/GNDSX +syllabication/M +syllabicity +syllabification/M +syllabify/GSDXN +syllable/SDMG +syllabub/M +syllabus/MS +syllabusss +syllogism/MS +syllogistic +sylph/M +sylphic +sylphlike +sylphs +sylvan/S +symbiont/M +symbioses +symbiosis/M +symbiotic +symbol/GMDS +symbolic/SM +symbolical/Y +symbolics/M +symbolism/MS +symbolist/MS +symbolization/MAS +symbolize/GZRSD +symbolized/U +symbolizes/A +symmetric +symmetrical/PY +symmetrically/U +symmetricalness/M +symmetrization/M +symmetrizing +symmetry/MS +sympathetic/S +sympathetically/U +sympathize/SRDJGZ +sympathized/U +sympathizer/M +sympathizing/MYUS +sympathy/MS +symphonic +symphonists +symphony/MS +symposium/MS +symptom/MS +symptomatic +symptomatically +symptomatology/M +syn +synagogal +synagogue/SM +synapse/SDGM +synaptic +sync/SGD +synchronism/M +synchronization's +synchronization/SA +synchronize/AGCDS +synchronized/U +synchronizer/MS +synchronous/YP +synchronousness/M +synchrony +synchrotron/M +syncopate/VNGXSD +syncopation/M +syncope/MS +syndic/SM +syndicalist +syndicate/XSDGNM +syndrome/SM +synergism/SM +synergistic +synergy/MS +synfuel/S +synod/SM +synonym/SM +synonymic +synonymous/Y +synonymy/MS +synopses +synopsis/M +synopsized +synopsizes +synopsizing +synoptic/S +syntactic/SY +syntactical/Y +syntactics/M +syntax/MS +syntheses +synthesis/M +synthesize/GZSRD +synthesized/U +synthesizer/M +synthesizes/A +synthetic/S +synthetically +syphilis/MS +syphilitic/S +syphilized +syphilizing +syringe/GMSD +syrup/DMSG +syrupy +sys +system/MS +systematic/SP +systematical/Y +systematics/M +systematization/SM +systematize/ZDRSG +systematized/U +systematizer/M +systematizing/U +systemic/S +systemically +systemization/SM +systole/MS +systolic +séance/SM +t/XTJBG +tab/SM +tabbed +tabbing +tabbouleh +tabboulehs +tabby/GSD +tabernacle/SDGM +tabla/MS +table/GMSD +tableau/M +tableaux +tablecloth/M +tablecloths +tableland/SM +tablespoon/SM +tablespoonful/MS +tablet/MDGS +tabletop/MS +tableware/SM +tabling/M +tabloid/MS +taboo/GSMD +tabor/MDGS +tabula +tabular/Y +tabulate/XNGDS +tabulation/M +tabulator/MS +tachometer/SM +tachometry +tachycardia/MS +tachyon/SM +tacit/YP +tacitness/MS +taciturn/Y +taciturnity/MS +tack/GZRDMS +tacker/M +tackiness/MS +tackle/RSDMZG +tackler/M +tackling/M +tacky/RSTP +taco/MS +tact/FSM +tactful/YP +tactfulness/S +tactic/SM +tactical/Y +tactician/MS +tactile/Y +tactility/S +tactless/PY +tactlessness/SM +tactual/Y +tad/SM +tadpole/MS +taffeta/MS +taffrail/SM +taffy/SM +tag/SM +tagged/U +tagger/S +tagging +taiga/MS +tail/CMRDGAS +tailback/MS +tailcoat/S +tailer/AM +tailgate/MGRSD +tailgater/M +tailing/MS +tailless/P +taillessness/M +taillight/MS +tailor/DMJSGB +tailpipe/SM +tailspin/MS +tailwind/SM +taint/DGS +tainted/U +take/RSHZGJ +takeaway/S +taken/A +takeoff/SM +takeout/S +takeover/SM +taker/M +takes/IA +taking/IA +talc/SM +talcked +talcking +talcum/S +tale/RSMN +talebearer/SM +talent/SMD +talented/M +talentless +taler/M +tali +talion/M +talisman/SM +talismanic +talk/GZSRD +talkative/YP +talkativeness/MS +talker/M +talkie/M +talky/RST +tall/TPR +tallboy/MS +tallish +tallness/MS +tallow/DMSG +tallowy +tally/GRSDZ +tallyho/DMSG +talon/SMD +talus/MS +tam/MDRSTZGB +tamable/M +tamale/SM +tamarack/SM +tamarind/MS +tambourine/MS +tame/SYP +tamed/U +tameness/S +tamp/SGZRD +tamper/ZGRD +tampered/U +tamperer/M +tampon/DMSG +tan/MS +tanager/MS +tanbark/SM +tandem/SM +tandoori/S +tang/GSYDM +tangelo/SM +tangency/M +tangent/SM +tangential/Y +tangerine/MS +tangibility/MIS +tangible/IPS +tangibleness's/I +tangibleness/SM +tangibly/I +tangle's +tangle/UDSG +tango/MDSG +tangy/RST +tank/GZSRDM +tankard/MS +tanker/M +tankful/MS +tanned/U +tanner/SM +tannery/MS +tannest +tannin/SM +tanning/SM +tansy/SM +tantalization/SM +tantalize/GZSRD +tantalized/U +tantalizing/YP +tantalizingly/S +tantalizingness/S +tantalum/MS +tantamount +tantra/S +tantrum/SM +tao/S +taoism +taoist/S +tap/MSDRJZG +tape/SM +taped/U +tapeline/S +taper/GRD +taperer/M +tapestry/GMSD +tapeworm/MS +tapioca/MS +tapir/MS +tapped/U +tapper/MS +tappet/MS +tapping/M +taproom/MS +taproot/SM +taps/M +tar/GSMD +tarantella/MS +tarantula/MS +tardily +tardiness/S +tardy/TPRS +tare/MS +target/GSMD +tariff/DMSG +tarmac/S +tarmacked +tarmacking +tarn/MS +tarnish/GDS +tarnished/U +taro/MS +tarot/MS +tarp/MS +tarpapered +tarpaulin/MS +tarpon/MS +tarragon/SM +tarred/M +tarring/M +tarry/TGRSD +tarsal/S +tarsi +tarsus/M +tart/PMYRDGTS +tartan/MS +tartar/SM +tartaric +tartness/MS +task/GSDM +taskmaster/SM +taskmistress/MS +tassel/MDGS +tassellings +taste's/E +taste/GZMJSRD +tasted/EU +tasteful/PEY +tastefulness/SME +tasteless/YP +tastelessness/SM +taster/M +tastes/E +tastily +tastiness/MS +tasting/E +tasty/RTP +tat/SRZ +tatami/MS +tater/M +tatted +tatter/GDS +tatterdemalion/SM +tattered/M +tatting/SM +tattle/RSDZG +tattler/M +tattletale/SM +tattoo/ZRDMGS +tattooer/M +tattooist/MS +tatty/R +tau/SM +taught/AU +taunt/ZGRDS +taunter/M +taunting/Y +taupe/SM +taut/PGTXYRDNS +tauten/GD +tautness/S +tautological/Y +tautologous +tautology/SM +tavern/RMS +taverner/M +tawdrily +tawdriness/SM +tawdry/SRTP +tawny/RSMPT +tax/ZGJMDRSB +taxable/S +taxably +taxation/MS +taxed/U +taxi/MDGS +taxicab/MS +taxidermist/SM +taxidermy/MS +taximeter/SM +taxing/Y +taxiway/MS +taxonomic +taxonomically +taxonomist/SM +taxonomy/SM +taxpayer/MS +taxpaying/M +tbs +tbsp +tea/MDGS +teabag/S +teacake/MS +teacart/M +teach/AGS +teachable/P +teacher/MS +teaching/SM +teacloth +teacup/MS +teacupful/MS +teahouse/SM +teak/SM +teakettle/SM +teakwood/M +teal/MS +tealeaves +team/MRDGS +teammate/MS +teamster/MS +teamwork/SM +teapot/MS +tear/RDMSG +tearaway +teardrop/MS +tearer/M +tearful/YP +tearfulness/M +teargas/S +teargassed +teargassing +tearjerker/S +tearoom/MS +teary/RT +teas/SRDGZ +tease/KS +teasel/DGSM +teaser/M +teashop/SM +teasing/Y +teaspoon/MS +teaspoonful/MS +teat/MDS +teatime/MS +tech/D +technetium/SM +technical/YSP +technicality/MS +technicalness/M +technician/MS +technique/SM +technocracy/MS +technocrat/S +technocratic +technological/Y +technologist/MS +technology/MS +technophobia +technophobic +techs +tectonic/S +tectonically +tectonics/M +teddy/SM +tedious/YP +tediousness/SM +tedium/MS +tee/DRSMH +teeing +teem/GSD +teeming/PY +teemingness/M +teen/SR +teenage/RZ +teenager/M +teeny/RT +teenybopper/SM +teepee's +teeshirt/S +teeter/GDS +teeth/RSDJMG +teethe +teether/M +teething/M +teethmarks +teetotal/SRDGZ +teetotaler/M +teetotalism/MS +tektite/SM +tel/SY +telecast/SRGZ +telecommunicate/NX +telecommunication/M +telecommute/SRDZGJ +telecoms +teleconference/GMJSD +telegenic +telegram/MS +telegrammed +telegramming +telegraph/MRDGZ +telegraphic +telegraphically +telegraphist/MS +telegraphs +telegraphy/MS +telekineses +telekinesis/M +telekinetic +telemarketer/S +telemarketing/S +telemeter/DMSG +telemetric +telemetry/MS +teleological/Y +teleology/M +telepathic +telepathically +telepathy/SM +telephone/SRDGMZ +telephonic +telephonist/SM +telephony/MS +telephoto/S +telephotography/MS +teleprinter/MS +teleprocessing/S +teleprompter +telescope/GSDM +telescopic +telescopically +teletext/S +telethon/MS +teletype/SM +teletypewriter/SM +televangelism/S +televangelist/S +televise/SDXNG +television/M +televisor/MS +televisual +telex/GSDM +tell/AGS +teller/SDMG +telling/YS +telltale/MS +tellurium/SM +telly/SM +telnet/S +telomeric +temblor/SM +temerity/MS +temp/SGZTMRD +temper's/E +temper/GRDM +tempera/SLM +temperament/SM +temperamental/Y +temperance/IMS +temperate/SDGPY +temperately/I +temperateness's/I +temperateness/SM +temperature/MS +tempered/UE +tempering/E +tempers/E +tempest/DMSG +tempestuous/PY +tempestuousness/SM +template's +template/FS +temple/SDM +tempo/MS +tempoes +temporal/YS +temporarily +temporariness/FM +temporarinesses +temporary/SFP +temporize/GJZRSD +temporizer/M +temporizing/YM +temporizings/U +tempt/FS +temptation/MS +tempted +tempter/S +tempting/YS +temptress/MS +tempura/SM +ten/MHB +tenabilities +tenability/UM +tenable/P +tenableness/M +tenably +tenacious/YP +tenaciousness/S +tenacity/S +tenancy/MS +tenant/MDSG +tenanted/U +tenantry/MS +tench/M +tend/ISFRDG +tended/UE +tendency/MS +tendentious/PY +tendentiousness/SM +tender/FS +tendered +tenderer +tenderest +tenderfoot/MS +tenderhearted/YP +tenderheartedness/MS +tendering +tenderize/SRDGZ +tenderizer/M +tenderloin/SM +tenderly +tenderness/SM +tending/E +tendinitis/S +tendon/MS +tendril/SM +tends/E +tenebrous +tenement/MS +tenet/SM +tenfold/S +tenner +tennis/SM +tenon/GSMD +tenor/MS +tenpin/SM +tens/SRDVGT +tense/IPYTNVR +tenseness's/I +tenseness/SM +tensile +tension's/I +tension/GMRDS +tensional/I +tensionless +tensions/E +tensity/IMS +tensor/MS +tensorial +tenspot +tent/FSIM +tentacle/MSD +tentative/SPY +tentativeness/S +tented/UF +tenter/M +tenterhook/MS +tenth/SY +tenths +tenting/F +tenuity/S +tenuous/YP +tenuousness/SM +tenure/SDM +tepee/MS +tepid/YP +tepidity/S +tepidness/S +tequila/SM +teratogenic +teratology/MS +terbium/SM +tercel/M +tercentenary/S +tercentennial/S +term/MYRDGS +termagant/SM +termcap +termer/M +terminable/CPI +terminableness/IMC +terminal/SYM +terminate/CXNV +terminated/U +terminates +terminating +termination/MC +terminative/YC +terminator/SM +termini +terminological/Y +terminology/MS +terminus/M +termite/SM +tern's +tern/GIDS +ternary/S +terpsichorean +terr/S +terrace/MGSD +terracing/M +terracotta +terrain/MS +terramycin +terrapin/MS +terrarium/MS +terrazzo/SM +terrestrial/YMS +terrible/P +terribleness/SM +terribly +terrier/M +terrific/Y +terrifically +terrify/GDS +terrifying/Y +terrine/M +territorial/SY +territoriality/M +territory/SM +terror/MS +terrorism/MS +terrorist/MS +terroristic +terrorize/RSDZG +terrorized/U +terrorizer/M +terry/ZMRS +terrycloth +terse/RTYP +terseness/SM +tertian +tertiary/S +tessellate/XDSNG +tessellation/M +tesseral +test's/AKF +test/RDBFZGSC +testability/M +testable/U +testament/SM +testamentary +testate/IS +testator/MS +testatrices +testatrix +testbed/S +testcard +tested/AKU +tester/MFCKS +testes/M +testicle/SM +testicular +testifier/M +testify/GZDRS +testily +testimonial/SM +testimony/SM +testiness/S +testing/S +testis/M +testosterone/SM +tests/AK +testy/RTP +tetanus/MS +tetchy/TR +tether/DMSG +tethered/U +tetra/MS +tetrachloride/M +tetracycline/SM +tetrafluoride +tetragonal/Y +tetrahalides +tetrahedral/Y +tetrahedron/SM +tetrameron +tetrameter/SM +tetrasodium +tetravalent +text/FSM +textbook/SM +textile/SM +textual/FY +textural/Y +texture/MGSD +textured/U +th/GNJX +thalami +thalamus/M +thalidomide/MS +thallium/SM +thallophyte/M +than +thane/SM +thank/SRDG +thanker/M +thankful/YP +thankfuller +thankfullest +thankfulness/SM +thankless/PY +thanklessness/SM +thanksgiving/MS +that'd +that'll +that/MS +thatch/JMDRSZG +thatching/M +thaumaturge/M +thaw/DGS +the +theater/SM +theatergoer/MS +theatergoing/MS +theatric/S +theatrical/YS +theatricality/SM +theatrics/M +thee/DS +theeing +theft/MS +their/MS +theism/SM +theist/SM +theistic +them/GD +themas +thematic/U +thematically +thematics +theme/MS +themselves +thence +thenceforth +thenceforward/S +theocracy/SM +theocratic +theodolite/MS +theologian/SM +theological/Y +theologists +theology/MS +theorem/MS +theoretic/S +theoretical/Y +theoretician/MS +theoretics/M +theorist/SM +theorization/SM +theorize/ZGDRS +theory/MS +theosophic +theosophical +theosophist/MS +theosophy/SM +therapeutic/S +therapeutically +therapeutics/M +therapist/MS +therapy/MS +there'd +there'll +there/MS +thereabout/S +thereafter +thereat +thereby +therefor +therefore +therefrom +therein +thereof +thereon +thereto +theretofore +thereunder +thereunto +thereupon +therewith +therm/MS +thermal/YS +thermionic/S +thermionics/M +thermistor/MS +thermo/S +thermocouple/MS +thermodynamic/S +thermodynamical/Y +thermodynamics/M +thermoelastic +thermoelectric +thermoformed +thermoforming +thermogravimetric +thermoluminescence/M +thermometer/MS +thermometric +thermometry/M +thermonuclear +thermopile/M +thermoplastic/S +thermopower +thermos/S +thermosetting +thermostable +thermostat/SM +thermostatic/S +thermostatically +thermostatics/M +thermostatted +thermostatting +thesauri +thesaurus/MS +these/S +thesis/M +thespian/S +theta/MS +thew/SM +they +they'd +they'll +they're +they've +thiamine/MS +thick/TXPSRNY +thicken/RDJZG +thickener/M +thickening/M +thicket/SMD +thickheaded/M +thickish +thickness/MS +thickset/S +thief/M +thieve/SDJG +thievery/MS +thievish/P +thievishness/M +thigh/DM +thighbone/SM +thighs +thimble/DSMG +thimbleful/MS +thin/STPYR +thine +thing/MP +thingamabob/MS +thingamajig/SM +think/AGRS +thinkable/U +thinkableness/M +thinkably/U +thinker/MS +thinking/SMYP +thinkingly/U +thinned +thinner/MS +thinness/MS +thinnest +thinning +thinnish +thiocyanate/M +thiouracil/M +third/DYGS +thirst/GSMDR +thirster/M +thirstily +thirstiness/S +thirsty/TPR +thirteen/MHS +thirteenths +thirtieths +thirty/HMS +this +this'll +thistle/SM +thistledown/MS +thither +tho +thole/GMSD +thong/SMD +thoracic +thorax/MS +thoriate/D +thorium/MS +thorn/SMDG +thorniness/S +thorny/PTR +thorough/PTYR +thoroughbred/S +thoroughfare/MS +thoroughgoing +thoroughness/SM +those +thou/DSG +though +thought/MS +thoughtful/U +thoughtfully +thoughtfulness/S +thoughtless/YP +thoughtlessness/MS +thousand/SHM +thousandfold +thousandths +thrall/GSMD +thralldom/S +thrash/DSRZGJ +thrasher/M +thrashing/M +thread/MZDRGS +threadbare/P +threader/M +threading/A +threadlike +thready/RT +threat/MDNSXG +threaten/GJRD +threatener/M +threatening/Y +three/MS +threefold +threepence/M +threepenny +threescore/S +threesome/SM +threnody/SM +thresh/DSRZG +thresher/M +threshold/MDGS +threw +thrice +thrift/SM +thriftily +thriftiness/S +thriftless +thrifty/PTR +thrill/ZMGDRS +thriller/M +thrilling/Y +thrive/RSDJG +thriver/M +thriving/Y +throat/MDSG +throatily +throatiness/MS +throaty/PRT +throb/S +throbbed +throbbing +throe/SDM +throeing +thrombi +thromboses +thrombosis/M +thrombotic +thrombus/M +throne's +throne/CGSD +throng/GDSM +throttle/DRSZMG +throttler/M +through/Y +throughout +throughput/SM +throughway's +throw/SZGR +throwaway/SM +throwback/MS +thrower/M +thrown +throwout +thrum/S +thrummed +thrumming +thrush/MS +thrust/ZGSR +thruster/M +thruway/SM +thud/MS +thudded +thudding +thug/MS +thuggee/M +thuggery/SM +thuggish +thulium/SM +thumb/SMDG +thumbnail/MS +thumbscrew/SM +thumbtack/GMDS +thump/RDMSG +thunder/ZGJDRMS +thunderbolt/MS +thunderclap/SM +thundercloud/SM +thunderer/M +thunderhead/SM +thundering/Y +thunderous/Y +thundershower/MS +thunderstorm/MS +thunderstruck +thundery +thunk +thus/Y +thwack/DRSZG +thwacker/M +thwart/GSDRY +thwarter/M +thy +thyme/SM +thymine/MS +thymus/SM +thyratron/M +thyristor/MS +thyroglobulin +thyroid/S +thyroidal +thyronine +thyrotoxic +thyrotrophic +thyrotrophin +thyrotropic +thyrotropin/M +thyroxine/M +thyself +ti/MDRZ +tiara/MS +tibia/M +tibiae +tibial +tic/MS +tick/GZJRDMS +ticker/M +ticket/SGMD +ticking/M +tickle/RSDZG +tickler/M +ticklish/PY +ticklishness/MS +ticktacktoe/S +ticktock/SMDG +tidal/Y +tidbit/MS +tiddlywinks/M +tide/GJDS +tideland/MS +tidewater/SM +tideway/SM +tidily/U +tidiness/USM +tidy/UGDSRPT +tidying/M +tie/AUDS +tieback/MS +tiebreaker/SM +tier/DGM +tiff/GDMS +tiffany/M +tiger/SM +tigerish +tight/STXPRNY +tighten/JZGDR +tightener/M +tightfisted +tightness/MS +tightrope/SM +tightwad/MS +tigress/SM +tike's +tilde/MS +tile/DRSJMZG +tiled/UE +tiles/U +tiling/M +till/EGSZDR +tillable +tillage/SM +tiller's/E +tiller/GDM +tilt/RDSGZ +tilth/M +timber/DMSG +timbering/M +timberland/SM +timberline/S +timbre/MS +timbrel/SM +time/DRSJMYZG +timebase +timekeeper/MS +timekeeping/SM +timeless/PY +timelessness/S +timeliness/SMU +timely/UTRP +timeout/S +timepiece/MS +timer/M +timescale/S +timeserver/MS +timeserving/S +timeshare/SDG +timespan +timestamped +timestamps +timetable/GMSD +timeworn +timezone/S +timid/RYTP +timidity/SM +timidness/MS +timing/M +timorous/YP +timorousness/MS +timothy/MS +timpani +timpanist/S +tin/MDGS +tincture/SDMG +tinder/MS +tinderbox/MS +tine/SM +tinfoil/MS +ting/GYDM +tinge/S +tingeing +tingle/SDG +tingling/Y +tingly/TR +tinily +tininess/MS +tinker/SRDMZG +tinkle/SDG +tinkling/M +tinkly +tinned +tinner/M +tinnily +tinniness/SM +tinning/M +tinnitus/MS +tinny/RSTP +tinplate/S +tinsel/GMDYS +tinsmith/M +tinsmiths +tint/SGMRDB +tinter/M +tintinnabulation/MS +tintype/SM +tinware/MS +tiny/RPT +tip/MS +tipi's +tipoff +tipped +tipper/MS +tippet/MS +tipping +tipple/ZGRSD +tippler/M +tippy/R +tipsily +tipsiness/SM +tipster/SM +tipsy/TPR +tiptoe/SD +tiptoeing +tiptop/S +tirade/SM +tire/MGDSJ +tired/AYP +tireder +tiredest +tiredness/S +tireless/PY +tirelessness/SM +tires/A +tiresome/PY +tiresomeness/S +tiring/AU +tiro's +tis +tissue/MGSD +tit/MRZS +titan/SM +titanate/M +titanic +titanically +titanium/SM +titbit's +titer/M +tithe/SRDGZM +tither/M +tithing/M +titian/S +titillate/XSDVNG +titillating/Y +titillation/M +titivate/NGDSX +titivation/M +title/GMSRD +titled/AU +titleholder/SM +titling/A +titmice +titmouse/M +titrate/SDGN +titration/M +titted +titter/GDS +titting +tittle/SDMG +titular/SY +tizzy/SM +tn +tnpk +to/D +toad/SM +toadstool/SM +toady/GSDM +toadyism/M +toast/SZGRDM +toaster/M +toastmaster/MS +toastmistress/S +toasty/TRS +tobacco/SM +tobacconist/SM +tobaggon/SM +toboggan/MRDSZG +toccata/M +tocsin/MS +today'll +today/SM +toddle/ZGSRD +toddler/M +toddy/SM +toe/MS +toecap/SM +toeclip/S +toehold/MS +toeing +toenail/DMGS +toffee/SM +tofu/S +tog/SMG +toga/SMD +toge +together/P +togetherness/MS +togged +togging +toggle/SDMG +toil/SGZMRD +toilet/GMDS +toiletry/MS +toilette/SM +toilsome/PY +toilsomeness/M +tokamak +toke/GDS +token/SMDG +tokenism/SM +tokenized +told/AU +tole/MGDS +tolerability/IM +tolerable/I +tolerably/I +tolerance/SIM +tolerant/IY +tolerate/XVNGSD +toleration/M +toll/DGS +tollbooth/M +tollbooths +tollgate/MS +tollhouse/M +tollway/S +toluene/MS +tom/SM +tomahawk/SGMD +tomato/M +tomatoes +tomb/GSDM +tomblike +tombola/M +tomboy/MS +tomboyish +tombstone/MS +tomcat/SM +tomcatted +tomcatting +tome/SM +tomfool/M +tomfoolery/MS +tommed +tomming +tommy/M +tomographic +tomography/MS +tomorrow/MS +tomtit/SM +ton/SKM +tonal/Y +tonality/MS +tone's +tone/ISRDZG +tonearm/S +toneless/YP +tonelessness/M +toner/IM +tong/GRDS +tongue/SDMG +tongueless +tonguing/M +tonic/SM +tonight/MS +tonk/MS +tonnage/SM +tonne/MS +tonsil/SM +tonsillectomy/MS +tonsillitis/SM +tonsorial +tonsure/SDGM +tony/RT +too/H +toodle +took/A +tool's +tool/AGDS +toolbox/SM +tooler/SM +tooling/M +toolkit/SM +toolmake/ZRG +toolmaker/M +toolmaking/M +toolsmith +toot/GRDZS +tooter/M +tooth/DMG +toothache/SM +toothbrush/MSG +toothily +toothless +toothmarks +toothpaste/SM +toothpick/MS +tooths +toothsome +toothy/TR +tootle/SRDG +toots/M +tootsie +tootsy/MS +top/SMDRG +topaz/MS +topcoat/MS +topdressing/S +toper/M +topflight +topgallant/M +topiary/S +topic/MS +topical/Y +topicality/MS +topknot/MS +topless +topmast/MS +topmost +topnotch/R +topocentric +topographer/SM +topographic +topographical/Y +topography/MS +topological/Y +topologist/MS +topology/MS +topped +topper/MS +topping/MS +topple/GSD +topsail/MS +topside/SRM +topsoil/GDMS +topspin/MS +toque/MS +tor/SLM +torch/SDMG +torchbearer/SM +torchlight/S +tore/S +toreador/SM +tori/M +torment/GSD +tormenting/Y +tormentor/MS +torn +tornado/M +tornadoes +toroid/MS +toroidal/Y +torpedo/GMD +torpedoes +torpid/SY +torpidity/S +torpor/MS +torque/MZGSRD +torrence +torrent/MS +torrential +torrid/RYTP +torridity/SM +torridness/SM +tors/S +torsi's +torsion/IAM +torsional/Y +torsions +torso/SM +tort's +tort/ASFE +torte/MS +tortellini/MS +torten +tortilla/MS +tortoise/SM +tortoiseshell/SM +tortoni/MS +tortuous/PY +tortuousness/MS +torture/ZGSRD +torturous +torus/MS +toss/SRDGZ +tossup/MS +tot/MDRSG +total/ZGSRDYM +totaler/M +totalistic +totalitarian/S +totalitarianism/SM +totality/MS +totalizator/S +totalizing +tote/S +totem/MS +totemic +toter/M +toting/M +totted +totter/ZGRDS +totterer/M +tottering/Y +totting +toucan/MS +touch/ASDG +touchable/U +touchdown/SM +touched/U +toucher/M +touchily +touchiness/SM +touching/SY +touchline/M +touchscreen +touchstone/SM +touchy/TPR +touché +tough/TXGRDNYP +toughen/DRZG +toughener/M +toughness/SM +toughs +toupee/SM +tour's/CF +tour/GZSRDM +toured/CF +tourer/M +touring/F +tourism/SM +tourist/SM +touristic +touristy +tourmaline/SM +tournament/MS +tourney/GDMS +tourniquet/MS +tours/CF +tousle/GSD +tout/SGRD +touter/M +tow/DRSZG +toward/YU +towardliness/M +towardly/P +towards +towboat/MS +towel/GJDMS +towelette/S +toweling/M +tower/GMD +towering/Y +towhead/MSD +towhee/SM +towline/MS +town/SRM +towner/M +townhouse/S +townie/S +townsfolk +township/MS +townsman/M +townsmen +townspeople/M +townswoman/M +townswomen +towpath/M +towpaths +towrope/MS +toxemia/MS +toxic/S +toxicity/MS +toxicological +toxicologist/SM +toxicology/MS +toxin/MS +toy/MDRSG +toyer/M +toymaker +toyshop +tr +trace's +trace/ASDG +traceability/M +traceable/P +traceableness/M +traceback/MS +traced/U +traceless/Y +tracepoint/SM +tracer/MS +tracery/MDS +trachea/M +tracheae +tracheal/M +tracheotomy/SM +tracing/SM +track/SZGMRD +trackage +trackball/S +trackbed +tracked/U +tracker/M +trackless +tracksuit/SM +tract's +tract/ABS +tractability/SI +tractable/I +tractably/I +traction/KSCEMAF +tractive/KFE +tractor/FKMASC +tracts/CEFK +trade/SRDGZM +trademark/GSMD +trader/M +tradesman/M +tradesmen +tradespeople +tradespersons +tradeswoman/M +tradeswomen +tradition/SM +traditional/U +traditionalism/MS +traditionalist/MS +traditionalistic +traditionalized +traditionally +traduce/DRSGZ +traffic/SM +trafficked +trafficker/MS +trafficking/S +tragedian/SM +tragedienne/MS +tragedy/MS +tragic/S +tragically +tragicomedy/SM +tragicomic +trail/SZGJRD +trailblazer/MS +trailblazing/S +trailer/GDM +trails/F +trailside +train/ASDG +trainable +trained/U +trainee/MS +traineeships +trainer/MS +training/SM +trainman/M +trainmen +trainspotter/S +traipse/DSG +trait/MS +traitor/SM +traitorous/Y +trajectory/MS +tram/MS +trammed +trammel/GSD +trammeled/U +tramming +tramp/RDSZG +trample/DGRSZ +trampler/M +trampoline/GMSD +tramway/M +trance/MGSD +tranche/SM +tranquil/PTRY +tranquility/S +tranquilize/JGZDSR +tranquilized/U +tranquilizer/M +tranquilizes/A +tranquilizing/YM +tranquillize/GRSDZ +tranquillizer/M +tranquilness/M +trans/I +transact/GSD +transaction/MS +transactional +transactor/SM +transalpine +transaminase +transatlantic +transceiver/SM +transcend/SDG +transcendence/MS +transcendent/Y +transcendental/YS +transcendentalism/SM +transcendentalist/SM +transconductance +transcontinental +transcribe/DSRGZ +transcriber/M +transcript/SM +transcription/SM +transcultural +transducer/SM +transduction/M +transect/DSG +transept/SM +transfer/BSMD +transferability/M +transferal/MS +transferee/M +transference/SM +transferor/MS +transferral/SM +transferred +transferrer/SM +transferring +transfiguration/SM +transfigure/SDG +transfinite/Y +transfix/SDG +transform/DRZBSG +transformation/MS +transformational +transformed/U +transformer/M +transfuse/XSDGNB +transfusion/M +transgress/VGSD +transgression/SM +transgressor/S +transience/SM +transiency/S +transient/YS +transistor/SM +transistorize/GDS +transit/SGVMD +transition/MDGS +transitional/Y +transitive/PIY +transitiveness/IM +transitivenesses +transitivity/MS +transitoriness/M +transitory/P +transl +translatability/M +translatable/U +translate/VGNXSDB +translated/AU +translation/M +translational +translator/SM +transliterate/XNGSD +translucence/SM +translucency/MS +translucent/Y +transmigrate/XNGSD +transmissible +transmission/MSA +transmissive +transmit/AS +transmittable +transmittal/SM +transmittance/MS +transmitted/A +transmitter/SM +transmitting/A +transmogrification/M +transmogrify/GXDSN +transmutation/SM +transmute/GBSD +transnational/S +transoceanic +transom/SM +transonic +transpacific +transparency/MS +transparent/YP +transparentness/M +transpiration/SM +transpire/GSD +transplant/GRDBS +transplantation/S +transpolar +transponder/MS +transport/BGZSDR +transportability +transportable/U +transportation/SM +transpose/BGSD +transposed/U +transposition/SM +transsexual/SM +transsexualism/MS +transship/LS +transshipment/SM +transshipped +transshipping +transubstantiation/MS +transversal/YM +transverse/GYDS +transvestism/SM +transvestite/SM +transvestitism +trap/MS +trapdoor/S +trapeze/DSGM +trapezium/MS +trapezoid/MS +trapezoidal +trappable/U +trapped +trapper/SM +trapping/S +trapshooting/SM +trash/SRDMG +trashcan/SM +trashiness/SM +trashy/TRP +trauma/MS +traumatic +traumatically +traumatize/SDG +travail/SMDG +travel/SDRGZJ +traveled/U +traveler/M +travelog's +travelogue/S +traversal/SM +traverse/GBDRS +traverser/M +travertine/M +travesty/SDGM +trawl/RDMSZG +trawler/M +tray/SM +treacherous/PY +treacherousness/SM +treachery/SM +treacle/DSGM +treacly +tread/SAGD +treader/M +treadle/GDSM +treadmill/MS +treas +treason/BMS +treasonous +treasure/DRSZMG +treasurer/M +treasurership +treasury/SM +treat's +treat/SAGDR +treatable +treated/U +treater/S +treatise/MS +treatment/MS +treaty/MS +treble/SDG +tree/MDS +treeing +treeless +treelike +treetop/SM +trefoil/SM +trek/MS +trekked +trekker/MS +trekking +trellis/GDSM +trematode/SM +tremble/JDRSG +trembler/M +trembles/M +trembly +tremendous/YP +tremendousness/M +tremolo/MS +tremor/MS +tremulous/YP +tremulousness/SM +trench's +trench/GASD +trenchancy/MS +trenchant/Y +trencher/SM +trencherman/M +trenchermen +trend/SDMG +trendily +trendiness/S +trendy/PTRS +trepanned +trepidation/MS +trespass/ZRSDG +trespasser/M +tress/MSDG +tressed/E +tresses/E +tressing/E +trestle/MS +trey/MS +triable/P +triableness/M +triad/MS +triadic +triage/SDMG +trial/ASM +trialization +trialled +trialling +triamcinolone +triangle/SM +triangulable +triangular/Y +triangularization/S +triangulate/YGNXSD +triangulation/M +triathlon/S +triatomic +tribal/Y +tribalism/MS +tribe/MS +tribesman/M +tribesmen +tribeswoman +tribeswomen +tribulate/NX +tribulation/M +tribunal/MS +tribune/SM +tributary/MS +tribute's +tribute/EGSF +trice/GSDM +tricentennial/S +triceps/SM +triceratops/M +trichina/M +trichinae +trichinoses +trichinosis/M +trichloroacetic +trichloroethane +trichotomy/M +trichromatic +trick/GMSRD +trickery/MS +trickily +trickiness/SM +trickle/DSG +trickster/MS +tricky/RPT +tricolor/SMD +tricycle/SDMG +trident/SM +tridiagonal +tried/UA +triennial/SY +trier's +trier/AS +tries/A +triffid/S +trifle/MZGJSRD +trifler/M +trifluoride/M +trifocals +trig/S +trigged +trigger/GSDM +triggest +trigging +triglyceride/MS +trigonal/Y +trigonometric +trigonometrical +trigonometry/MS +trigram/S +trihedral +trike/GMSD +trilateral/S +trilby/SM +trilingual +trill/RDMGS +trillion/SMH +trillionth/M +trillionths +trillium/SM +trilobite/MS +trilogy/MS +trim/PSYR +trimaran/MS +trimer/M +trimester/MS +trimmed/U +trimmer/MS +trimmest +trimming/MS +trimness/S +trimodal +trimonthly +trinitarian/S +trinitrotoluene/SM +trinity/MS +trinket/MRDSG +trinketer/M +trio/SM +triode/MS +trioxide/M +trip/SMY +tripartite/N +tripartition/M +tripe/MS +triphenylarsine +triphenylphosphine +triphenylstibine +triphosphopyridine +triple/GSD +triplet/SM +triplex/S +triplicate/SDG +triplication/M +triply/GDSN +tripod/MS +tripodal +tripoli/M +tripolyphosphate +tripos/SM +tripped +tripper/MS +tripping/Y +triptych/M +triptychs +tripwire/MS +trireme/SM +trisect/GSD +trisection/S +trisector +trisodium +tristate +trisyllable/M +trite/SRPTY +tritely/F +triteness/SF +tritium/MS +triton/M +triumph/GMD +triumphal +triumphalism +triumphant/Y +triumphs +triumvir/MS +triumvirate/MS +triune +trivalent +trivet/SM +trivia +trivial/Y +triviality/MS +trivialization/MS +trivialize/DSG +trivium/M +trochaic/S +trochee/SM +trod/AU +trodden/UA +trodes +troff/MR +troglodyte/MS +troika/SM +troll/DMSG +trolled/F +trolley/SGMD +trolleybus/S +trolling/F +trollish +trollop/GSMD +trolly's +trombone/MS +trombonist/SM +tromp/DSG +troop/SRDMZG +trooper/M +troopship/SM +trope/SM +trophic +trophy/MGDS +tropic/MS +tropical/SY +tropism/SM +tropocollagen +troposphere/MS +tropospheric +trot/S +troth/GDM +troths +trotted +trotter/SM +trotting +troubadour/SM +trouble/GDRSM +troubled/U +troublemaker/MS +troubler/M +troubleshoot/SRDZG +troubleshooter/M +troubleshot +troublesome/YP +troublesomeness/M +trough/M +troughs +trounce/GZDRS +trouncer/M +troupe/MZGSRD +trouper/M +trouser/DMGS +trousseau/M +trousseaux +trout/SM +trove/SM +trow/SGD +trowel/SMDRGZ +troweler/M +troy/S +truancy/MS +truant/SMDG +truce/SDGM +truck/SZGMRDJ +trucker/M +trucking/M +truckle/GDS +truckload/MS +truculence/SM +truculent/Y +trudge/SRDG +true/DRSPTG +truelove/MS +trueness/M +truer/U +truest/U +truffle/MS +truism/SM +truly/U +trump/DMSG +trumpery/SM +trumpet/MDRZGS +trumpeter/M +truncate/NGDSX +truncation/M +truncheon/MDSG +trundle/GZDSR +trundler/M +trunk/GSMD +trunnion/SM +truss/SRDG +trusser/M +trussing/M +trust/RDMSG +trusted/EU +trustee/MDS +trusteeing +trusteeship/SM +truster/M +trustful/EY +trustfulness/SM +trustiness/M +trusting/Y +trusts/E +trustworthier +trustworthiest +trustworthiness/MS +trustworthy/UP +trusty/PTMSR +truth/UM +truthful/UYP +truthfulness/US +truths/U +try/JGDRSZ +trying/Y +tryout/MS +trypsin/M +tryst/GDMS +ts +tsarevich +tsarina's +tsarism/M +tsarist +tsetse/S +tsp +tsunami/MS +tty/M +ttys +tub/JMDRSZG +tuba/SM +tubae +tubal +tubbed +tubbing +tubby/TR +tube/SM +tubeless +tuber/M +tubercle/MS +tubercular/S +tuberculin/MS +tuberculoses +tuberculosis/M +tuberculous +tuberose/SM +tuberous +tubing/M +tubular/Y +tubule/SM +tuck/GZSRD +tucker/GDM +tuft/GZSMRD +tufter/M +tufting/M +tug/S +tugboat/MS +tugged +tugging +tuition/ISM +tularemia/S +tulip/SM +tulle/SM +tum +tumble/ZGRSDJ +tumbledown +tumbler/M +tumbleweed/MS +tumbrel/SM +tumescence/S +tumescent +tumid/Y +tumidity/MS +tummy/SM +tumor/MDS +tumorous +tumult/SGMD +tumultuous/PY +tumultuousness/M +tumulus/M +tun/DRJZGBS +tuna/SM +tunable/P +tunableness/M +tundra/SM +tune's +tune/CSDG +tuneful/YP +tunefulness/MS +tuneless/Y +tuner/M +tuneup/S +tung +tungstate/M +tungsten/SM +tunic/MS +tuning's +tuning/A +tunned +tunnel/MRDSJGZ +tunneler/M +tunning +tunny/SM +tupelo/M +tuple/SM +tuppence/M +turban/SDM +turbid +turbidity/SM +turbinate/SD +turbine/SM +turbo/SM +turbocharged +turbocharger/SM +turbofan/MS +turbojet/MS +turboprop/MS +turbot/MS +turbulence/SM +turbulent/Y +turd/MS +tureen/MS +turf/DGSM +turfy/RT +turgid/PY +turgidity/SM +turgidness/M +turk/S +turkey/SM +turmeric/MS +turmoil/SDMG +turn/AZGRDBS +turnabout/SM +turnaround/MS +turnbuckle/SM +turncoat/SM +turned/U +turner/M +turning/MS +turnip/SMDG +turnkey/MS +turnoff/MS +turnout/MS +turnover/SM +turnpike/MS +turnround/MS +turnstile/SM +turnstone/M +turntable/SM +turpentine/GMSD +turpitude/SM +turquoise/SM +turret/SMD +turtle/SDMG +turtleback/MS +turtledove/MS +turtleneck/SDM +turves's +turvy +tush/SDG +tusk/GZRDMS +tusker/M +tussle/GSD +tussock/MS +tussocky +tut/S +tutelage/MS +tutelary/S +tutor/MDGS +tutored/U +tutorial/MS +tutorship/S +tutted +tutti/S +tutting +tutu/SM +tux/S +tuxedo/SDM +twaddle/GZMRSD +twaddler/M +twain/S +twang/MDSG +twangy/TR +twas +tweak/SGRD +twee/DP +tweed/SM +tweediness/M +tweedy/PTR +tween +tweet/ZSGRD +tweeter/M +tweeze/ZGRD +tweezer/M +twelfth +twelfths +twelve/MS +twelvemonth/M +twelvemonths +twentieths +twenty/MSH +twerp/MS +twice/R +twiddle/GRSD +twiddler/M +twiddly/RT +twig/SM +twigged +twigging +twiggy/RT +twilight/MS +twilit +twill/SGD +twin/RDMGZS +twine/SM +twiner/M +twinge/SDMG +twinkle/RSDG +twinkler/M +twinkling/M +twinkly +twinned +twinning +twirl/SZGRD +twirler/M +twirling/Y +twirly/TR +twist/SZGRD +twisted/U +twister/M +twists/U +twisty +twit/S +twitch/GRSD +twitchy/TR +twitted +twitter/SGRD +twitterer/M +twittery +twitting +twixt +two/MS +twofer/MS +twofold/S +twopence/SM +twopenny/S +twosome/MS +twp +tycoon/MS +tyeing +tying/UA +tyke/SM +tympani +tympanist/SM +tympanum/SM +type/MGDRSJ +typeahead +typecast/SG +typed/AU +typedef/S +typeface/MS +typeless +types/A +typescript/SM +typeset/S +typesetter/MS +typesetting/SM +typewrite/SRJZG +typewriter/M +typewriting/M +typewritten +typewrote +typhoid/SM +typhoon/SM +typhus/SM +typical/U +typicality/MS +typically +typicalness/M +typification/M +typify/SDNXG +typing/A +typist/MS +typo/MS +typographer/SM +typographic +typographical/Y +typography/MS +typological/Y +typology/MS +tyrannic +tyrannical/PY +tyrannicalness/M +tyrannicide/M +tyrannize/ZGJRSD +tyrannizer/M +tyrannizing/YM +tyrannosaur/MS +tyrannosaurus/S +tyrannous +tyranny/MS +tyrant/MS +tyreo +tyro/SM +tyrosine/M +tzar's +tzarina's +u +ubiquitous/YP +ubiquity/S +udder/SM +ufologist/S +ufology/MS +ugh +ughs +uglification +ugliness/MS +uglis +ugly/PTGSRD +uh +ukase/SM +ukulele/SM +ulcer/MDGS +ulcerate/NGVXDS +ulceration/M +ulcerous +ulna/M +ulnae +ulnar +ulster/MS +ult +ulterior/Y +ultimas +ultimate/DSYPG +ultimateness/M +ultimatum/MS +ultimo +ultra/S +ultracentrifugally +ultracentrifugation +ultracentrifuge/M +ultraconservative/S +ultrafast +ultrahigh +ultralight/S +ultramarine/SM +ultramodern +ultramontane +ultrashort +ultrasonic/S +ultrasonically +ultrasonics/M +ultrasound/SM +ultrastructure/M +ultraviolet/SM +ululate/DSXGN +ululation/M +um +umbel/MS +umber/GMDS +umbilical/S +umbilici +umbilicus/M +umbra/MS +umbrage/MGSD +umbrageous +umbrella/GDMS +umiak/MS +umlaut/GMDS +ump/MDSG +umpire/MGSD +umpteen/H +unabated/Y +unabridged/S +unacceptability +unacceptable +unaccepted +unaccommodating +unaccountability +unaccustomed/Y +unadapted +unadulterated/Y +unadventurous +unalienability +unalterable/P +unalterableness/M +unalterably +unambiguity +unambiguous +unambitious +unamused +unanimity/SM +unanimous/Y +unanticipated/Y +unapologetic +unapologizing/M +unappeasable +unappeasably +unappreciative +unary +unassailable/P +unassailableness/M +unassertive +unassuming/PY +unassumingness/M +unauthorized/PY +unavailing/PY +unaware/SPY +unbalanced/P +unbar +unbarring +unbecoming/P +unbeknown +unbelieving/Y +unbiased/P +unbid +unbind/G +unblessed +unblinking/Y +unbodied +unbolt/G +unbreakability +unbred +unbroken +unbuckle +unbudging/Y +unburnt +uncap +uncapping +uncatalogued +uncauterized/MS +unceasing/Y +uncelebrated +uncertain/P +unchallengeable +unchanging/PY +unchangingness/M +uncharacteristic +uncharismatic +unchastity +unchristian +uncial/S +uncivilized/Y +unclassified +uncle/MSD +unclouded/Y +uncodable +uncollected +uncolored/PY +uncoloredness/M +uncombable +uncommunicative +uncompetitive +uncomplicated +uncomprehending/Y +uncompromisable +unconcern/M +unconcerned/P +unconfirmed +unconfused +unconscionable/P +unconscionableness/M +unconscionably +unconstitutional +unconsumed +uncontentious +uncontrollability +unconvertible +uncool +uncooperative +uncork/G +uncouple/G +uncouth/YP +uncouthness/M +uncreate/V +uncritical +uncross/GB +uncrowded +unction/IM +unctions +unctuous/PY +unctuousness/MS +uncustomary +uncut +undated/I +undaunted/Y +undeceive +undecided/S +undedicated +undefinability +undefined/P +undefinedness/M +undelete +undeliverability +undeniable/P +undeniableness/M +undeniably +undependable +under/Y +underachieve/SRDGZ +underachiever/M +underact/GDS +underadjusting +underage/S +underarm/DGS +underbedding +underbelly/MS +underbid/S +underbidding +underbracing +underbrush/MSDG +undercarriage/MS +undercharge/GSD +underclass/S +underclassman +underclassmen +underclothes +underclothing/MS +undercoat/JMDGS +undercoating/M +underconsumption/M +undercooked +undercount/S +undercover +undercurrent/SM +undercut/S +undercutting +underdeveloped +underdevelopment/MS +underdog/MS +underdone +undereducated +underemphasis +underemployed +underemployment/SM +underenumerated +underenumeration +underestimate/NGXSD +underexploited +underexpose/SDG +underexposure/SM +underfed +underfeed/SG +underfloor +underflow/GDMS +underfoot +underfund/DG +underfur/MS +undergarment/SM +undergirding +undergo/G +undergoes +undergone +undergrad/MS +undergraduate/MS +underground/RMS +undergrowth/M +undergrowths +underhand/D +underhanded/YP +underhandedness/MS +underheat +underinvestment +underlaid +underlain/S +underlay/GS +underlie +underline/GSDJ +underling/MS +underlip/SM +underloaded +underly/GS +undermanned +undermentioned +undermine/SDG +undermost +underneath +underneaths +undernourished +undernourishment/SM +underpaid +underpants +underpart/MS +underpass/SM +underpay/GSL +underpayment/SM +underperformed +underpin/S +underpinned +underpinning/MS +underplay/SGD +underpopulated +underpopulation/M +underpowered +underpricing +underprivileged +underproduction/MS +underrate/GSD +underregistration/M +underreported +underreporting +underrepresentation/M +underrepresented +underscore/SDG +undersea/S +undersealed +undersecretary/SM +undersell/SG +undersexed +undershirt/SM +undershoot/SG +undershorts +undershot +underside/SM +undersign/SGD +undersigned/M +undersized +undersizes +undersizing +underskirt/MS +undersold +underspecification +underspecified +underspend/G +understaffed +understand/RGSJB +understandability/M +understandably +understanding/YM +understate/GSDL +understatement/MS +understocked +understood +understrength +understructure/SM +understudy/GMSD +undertake/SRGZJ +undertaken +undertaker/M +undertaking/M +underthings +undertone/SM +undertook +undertow/MS +underused +underusing +underutilization/M +underutilized +undervaluation/S +undervalue/SDG +underwater/S +underway +underwear/M +underweight/S +underwent +underwhelm/DGS +underwood/M +underworld/MS +underwrite/GZSR +underwriter/M +underwritten +underwrote +undeserving +undesigned +undeviating/Y +undialyzed/SM +undiplomatic +undiscerning +undiscriminating +undo/GJ +undoubted/Y +undramatic +undramatized/SM +undress/G +undrinkability +undrinkable +undroppable +undue +undulant +undulate/XDSNG +undulation/M +unearth/YG +unearthliness/S +unearthly/P +unease +uneconomic +uneducated +unemployed/S +unencroachable +unending/Y +unendurable/P +unenergized/MS +unenforced +unenterprising +unethical +uneulogized/SM +unexacting +unexceptionably +unexcited +unexpectedness/MS +unfading/Y +unfailing/P +unfailingness/M +unfamiliar +unfashionable +unfathomably +unfavored +unfeeling +unfeigned/Y +unfelt +unfeminine +unfertile +unfetchable +unflagging +unflappability/S +unflappable +unflappably +unflinching/Y +unfold/LG +unfoldment/M +unforced +unforgeable +unfossilized/MS +unfraternizing/SM +unfrozen +unfulfillable +unfunny +unfussy +ungainliness/MS +ungainly/PRT +ungenerous +ungentle +unglamorous +ungrammaticality +ungrudging +unguent/MS +ungulate/MS +unharmonious +unharness/G +unhistorical +unholy/TP +unhook/DG +unhydrolyzed/SM +unhygienic +unicameral +unicellular +unicorn/SM +unicycle/MGSD +unicyclist/MS +unideal +unidimensional +unidiomatic +unidirectional/Y +unidirectionality +unidolized/MS +unifiable +unification/MA +unifier/MS +unifilar +uniform/TGSRDYMP +uniformity/MS +uniformness/M +unify/AXDSNG +unilateral/Y +unilateralism/M +unilateralist +unimodal +unimpeachably +unimportance +unimportant +unimpressive +unindustrialized/MS +uninhibited/YP +uninominal +uninsured +unintellectual +unintended +uninteresting +uninterrupted/YP +uninterruptedness/M +unintuitive +uninviting +union/AEMS +unionism/SM +unionist/SM +unionize +unipolar +uniprocessor/SM +unique/TYSRP +uniqueness/S +unisex/S +unison/MS +unit/VGRD +unitarian/MS +unitarianism/M +unitary +unite/AEDSG +united/Y +uniter/M +unitize/GDS +unity/SEM +univ +univalent/S +univalve/MS +univariate +universal/YSP +universalism/M +universalistic +universality/SM +universalize/DSRZG +universalizer/M +universe/MS +university/MS +unjam +unkempt +unkind/TP +unkink +unknightly +unknowable/S +unknowing +unlabored +unlace/G +unlearn/G +unlikeable +unlikeliness/S +unlimber/G +unlimited +unlit +unliterary +unloose/G +unlucky/TP +unmagnetized/MS +unmanageably +unmannered/Y +unmask/G +unmeaning +unmeasured +unmeetable +unmelodious +unmemorable +unmemorialized/MS +unmentionable/S +unmerciful +unmeritorious +unmethodical +unmineralized/MS +unmissable +unmistakably +unmitigated/YP +unmnemonic +unmobilized/SM +unmoral +unmount/B +unmovable +unmoving +unnaturalness/M +unnavigable +unnerving/Y +unobliging +unoffensive +unofficial +unorganized/YP +unorthodox +unpack/G +unpaintable +unpalatability +unpalatable +unpartizan +unpatronizing +unpeople +unperceptive +unperson +unperturbed/Y +unphysical +unpick/G +unpicturesque +unpinning +unpleasing +unploughed +unpolarized/SM +unpopular +unpractical +unprecedented/Y +unpredictable/S +unpreemphasized +unpremeditated +unpretentiousness/M +unprincipled/P +unproblematic +unproductive +unpropitious +unprovable +unproven +unprovocative +unpunctual +unquestionable +unraisable +unravellings +unread/B +unreadability +unreal +unrealizable +unreasoning/Y +unreceptive +unrecordable +unreflective +unrelenting/Y +unremitting/Y +unrepeatability +unrepeated +unrepentant +unreported +unrepresentative +unreproducible +unrest/G +unrestrained/P +unrewarding +unriddle +unripe/P +unromantic +unruliness/SM +unruly/PTR +unsaleable +unsanitary +unsavored/YP +unsavoriness/M +unseal/GB +unsearchable +unseasonal +unseeing/Y +unseen/S +unselfconscious/P +unselfconsciousness/M +unselfishness/M +unsellable +unsentimental +unset +unsettled/P +unsettledness/M +unsettling/Y +unshapely +unshaven +unshorn +unsighted +unsightliness/S +unskilful +unsociability +unsociable/P +unsocial +unsound/PT +unspeakably +unspecific +unspectacular +unspoilt +unspoke +unsporting +unstable/P +unstigmatized/SM +unstilted +unstinting/Y +unstopping +unstrapping +unstudied +unstuffy +unsubdued +unsubstantial +unsubtle +unsuitable +unsuspecting/Y +unswerving/Y +unsymmetrical +unsympathetic +unsystematic +unsystematized/Y +untactful +untalented +untaxing +unteach/B +untellable +untenable +unthinking +until/G +untiring/Y +unto +untouchable/MS +untoward/P +untowardness/M +untraceable +untrue +untruthfulness/M +untwist/G +unusualness/M +unutterable +unutterably +unvocalized/MS +unvulcanized/SM +unwaivering +unwarrantable +unwarrantably +unwashed/PS +unwearable +unwearied/Y +unwed +unwedge +unwelcome +unwell/M +unwieldiness/MS +unwieldy/TPR +unwind/B +unwomanly +unworkable/S +unworried +unwrap +unwrapping +unyielding/Y +unyoke +unzip +up +uparrow +upbeat/SM +upbraid/GDRS +upbring/JG +upbringing/M +upchuck/SDG +upcome/G +upcountry/S +updatability +update/RSDG +updater/M +updraft/SM +upend/SDG +upfield +upfront +upgrade/DSJG +upgradeable +upheaval/MS +upheld +uphill/S +uphold/RSGZ +upholder/M +upholster/ADGS +upholsterer/SM +upholstery/MS +upkeep/SM +upland/MRS +uplander/M +uplift/SJDRG +uplifter/M +upload/GSD +upmarket +upon +upped +upper/S +uppercase/GSD +upperclassman/M +upperclassmen +uppercut/S +uppercutting +uppermost +upping +uppish +uppity +upraise/GDS +uprated +uprating +uprear/DSG +upright/DYGSP +uprightness/S +uprise/RGJ +uprising/M +upriver/S +uproar/MS +uproarious/PY +uproariousness/M +uproot/DRGS +uprooter/M +ups +upscale/GDS +upset/S +upsetting/MS +upshot/SM +upside/MS +upsilon/MS +upslope +upstage/DSRG +upstairs +upstanding/P +upstandingness/M +upstart/MDGS +upstate/SR +upstream/DSG +upstroke/MS +upsurge/DSG +upswing/GMS +upswung +uptake/SM +upthrust/GMS +uptight +uptime +uptown/RS +uptrend/M +upturn/GDS +upward/SYP +upwardness/M +upwelling +upwind/S +uracil/MS +uranium/MS +uranyl/M +urban/RT +urbane/Y +urbanism/M +urbanite/SM +urbanity/SM +urbanization/MS +urbanize/DSG +urbanologist/S +urbanology/S +urchin/SM +urea/SM +uremia/MS +uremic +ureter/MS +urethane/MS +urethra/M +urethrae +urethral +urethritis/M +urge/GDRSJ +urgency/SM +urgent/Y +urger/M +uric +urinal/MS +urinalyses +urinalysis/M +urinary/MS +urinate/XDSNG +urination/M +urine/MS +urn/MDGS +urning/M +urogenital +urological +urologist/S +urology/MS +ursine +urticaria/MS +us/DRSBZG +usability/S +usable/U +usably/U +usage/SM +use/ESDAG +used/U +useful/YP +usefulness/SM +useless/PY +uselessness/MS +user/M +usher/SGMD +usherette/SM +usu +usual/UPY +usuals +usurer/SM +usurious/PY +usuriousness/M +usurp/RDZSG +usurpation/MS +usurper/M +usury/SM +utensil/SM +uteri +uterine +uterus/M +utile/I +utilitarian/S +utilitarianism/MS +utility/MS +utilization's/A +utilization/MS +utilize/GZDRS +utilizer/M +utilizes/A +utmost/S +utopia/S +utopian's +utopianism/M +utter/TRDYGS +utterance/MS +uttered/U +utterer/M +uttermost/S +uucp/M +uvula/MS +uvular/S +uxorious +v/ASV +vacancy/MS +vacant/PY +vacantness/M +vacate/NGXSD +vacation/MRDZG +vacationist/SM +vacationland +vaccinate/NGSDX +vaccination/M +vaccine/SM +vaccinia/M +vaccinial +vacillate/XNGSD +vacillating/Y +vacillation/M +vacillator/SM +vacua's +vacuity/MS +vacuo +vacuolate/SDGN +vacuolated/U +vacuole/SM +vacuolization/SM +vacuous/PY +vacuousness/MS +vacuum/GSMD +vagabond/DMSG +vagabondage/MS +vagarious +vagary/MS +vagina/M +vaginae +vaginal/Y +vagrancy/MS +vagrant/SMY +vague/TYSRDP +vagueing +vagueness/MS +vain/TYRP +vainglorious/YP +vaingloriousness/M +vainglory/MS +val +valance/SDMG +vale/SM +valediction/MS +valedictorian/MS +valedictory/MS +valence/SM +valency/MS +valentine/SM +valet/GDMS +valetudinarian/MS +valetudinarianism/MS +valiance/S +valiant/SPY +valiantness/M +valid/PIY +validate/INGSDX +validated/AU +validates/A +validation/AMI +validity/IMS +validness/MI +validnesses +valise/MS +valley/SM +valor/MS +valorous/Y +valuable/IP +valuableness/IM +valuables +valuably/I +valuate/NGXSD +valuation/CSAM +valuator/SM +value's +value/CGASD +valued/U +valueless/P +valuelessness/M +valuer/SM +values/E +valve/GMSD +valveless +valvular +vamoose/GSD +vamp's +vamp/ADSG +vamper +vampire/MGSD +van/SMD +vanadium/MS +vandal/MS +vandalism/MS +vandalize/GSD +vane/MS +vanguard/MS +vanilla/MS +vanish/GRSDJ +vanisher/M +vanishing/Y +vanity/SM +vanned +vanning +vanquish/RSDGZ +vanquisher/M +vantage/MS +vapid/PY +vapidity/MS +vapidness/SM +vapor/MRDJGZS +vaporer/M +vaporing/MY +vaporisation +vaporise/DSG +vaporization/AMS +vaporize/DRSZG +vaporizer/M +vaporous +vapory +vaquero/SM +var/S +variability/IMS +variable/PMS +variableness/IM +variables/I +variably/I +variance's +variance/I +variances +variant/ISY +variate/MGNSDX +variation/M +variational +varicolored/MS +varicose/S +varied/U +variedly +variegate/NGXSD +variegation/M +varier/M +varietal/S +variety/MS +various/PY +varistor/M +varlet/MS +varmint/SM +varnish/ZGMDRS +varnished/U +varnisher/M +varsity/MS +vary/SRDJG +varying/UY +vascular +vase/SM +vasectomy/SM +vasomotor +vassal/GSMD +vassalage/MS +vast/PTSYR +vastness/MS +vat/SM +vatted +vatting +vaudeville/SM +vaudevillian/SM +vault/ZSRDMGJ +vaulter/M +vaulting/M +vaunt/GRDS +vaunter/M +vb +veal/MRDGS +vealed/A +vealer/MA +veals/A +vector's/F +vector/SGDM +vectorial +vectorization +vectorized +vectorizing +veejay/S +veep/S +veer/DSG +veering/Y +veg/M +vegan/SM +veges +vegetable/MS +vegetarian/SM +vegetarianism/MS +vegetate/DSNGVX +vegetation/M +vegetative/PY +vegged +veggie/S +vegging +vehemence/MS +vehemency/S +vehement/Y +vehicle/SM +vehicular +veil's +veil/UGSD +veiling/MU +vein/GSRDM +veining/M +vela/M +velar/S +velarize/SDG +veld/SM +veldt's +vellum/MS +velocipede/SM +velocity/SM +velor/S +velour's +velum/M +velvet/GSMD +velveteen/MS +velvety/RT +venal/Y +venality/MS +venation/SM +vend/DSG +vender's/K +vendetta/MS +vendible/S +vendor/MS +veneer/GSRDM +veneerer/M +veneering/M +venerability/S +venerable/P +venerate/XNGSD +veneration/M +venereal +venetian +vengeance/MS +vengeful/APY +vengefulness/AM +venial/YP +venialness/M +venireman/M +veniremen +venison/SM +venom/SGDM +venomous/YP +venomousness/M +venous/Y +vent's/F +vent/ISGFD +venter/M +ventilate/XSDVGN +ventilated/U +ventilation/M +ventilator/MS +ventral/YS +ventricle/MS +ventricular +ventriloquies +ventriloquism/MS +ventriloquist/MS +ventriloquy +venture/RSDJZG +venturesome/YP +venturesomeness/SM +venturi/S +venturous/YP +venturousness/MS +venue/MAS +veracious/YP +veraciousness/M +veracities +veracity/IM +veranda/SDM +verandahed +verb/KSM +verbal/SY +verbalization/MS +verbalize/ZGRSD +verbalized/U +verbalizer/M +verballed +verballing +verbatim +verbena/MS +verbiage/SM +verbose/YP +verbosity/SM +verboten +verdant/Y +verdict/SM +verdigris/GSDM +verdure/SDM +verge's +verge/FGSD +verger/SM +veridical/Y +verifiability/M +verifiable/U +verifiableness/M +verification/S +verified/U +verifier/MS +verify/GASD +verily +verisimilitude/SM +veritable/P +veritableness/M +veritably +verity/MS +vermicelli/MS +vermiculite/MS +vermiform +vermilion/MS +vermin/M +verminous +vermouth/M +vermouths +vernacular/YS +vernal/Y +vernier/SM +veronica/SM +verruca/MS +verrucae +versa +versatile/YP +versatileness/M +versatility/SM +verse's +verse/XSRDAGNF +versed/UI +verses/I +versicle/M +versification/M +versifier/M +versify/GDRSZXN +versing/I +version/MFISA +verso/SM +versus +vertebra/M +vertebrae +vertebral/Y +vertebrate/IMS +vertebration/M +vertex/SM +vertical/YPS +vertices's +vertiginous +vertigo/M +vertigoes +verve/SM +very/RT +vesicle/SM +vesicular/Y +vesiculate/GSD +vesper/SM +vessel/MS +vest's +vest/DIGSL +vestal/YS +vestibular +vestibule/SDM +vestige/SM +vestigial/Y +vesting/SM +vestment/ISM +vestry/MS +vestryman/M +vestrymen +vesture/SDMG +vet/SMR +vetch/SM +veter/M +veteran/SM +veterinarian/MS +veterinary/S +veto/DMG +vetoes +vetted +vetting/A +vex/GFSD +vexation/SM +vexatious/PY +vexatiousness/M +vexed/Y +vhf +vi/MDR +via +viability/SM +viable/I +viably +viaduct/MS +vial/MDGS +viand/SM +vibe/S +vibraharp/MS +vibrancy/MS +vibrant/YS +vibraphone/MS +vibraphonist/SM +vibrate/XNGSD +vibration/M +vibrational/Y +vibrato/MS +vibrator/SM +vibratory +vibrio/M +vibrionic +viburnum/SM +vicar/SM +vicarage/SM +vicarious/YP +vicariousness/MS +vice/CMS +viced +vicegerent/MS +vicennial +viceregal +viceroy/SM +vichyssoise/MS +vicing +vicinity/MS +vicious/YP +viciousness/S +vicissitude/MS +victim/SM +victimization/SM +victimize/SRDZG +victimized/U +victimizer/M +victor/SM +victorious/YP +victoriousness/M +victory/MS +victual/ZGSDR +victualer/M +vicuña/S +videlicet +video/GSMD +videocassette/S +videoconferencing +videodisc/S +videodisk/SM +videophone/SM +videotape/SDGM +vie/S +vier/M +view/MBGZJSRD +viewed/A +viewer's +viewer/AS +viewfinder/MS +viewgraph/SM +viewing/M +viewless/Y +viewpoint/SM +views/A +vigesimal +vigil/SM +vigilance/MS +vigilant/Y +vigilante/SM +vigilantism/MS +vigilantist +vignette/MGDRS +vignetter/M +vignetting/M +vignettist/MS +vigor/MS +vigorous/YP +vigorousness/M +vii +viii +viking/S +vile/AR +vilely +vileness/MS +vilest +vilification/M +vilifier/M +vilify/GNXRSD +villa/MS +village/RSMZ +villager/M +villain/SM +villainous/YP +villainousness/M +villainy/MS +ville +villein/MS +villeinage/SM +villi +villus/M +vim/MS +vinaigrette/MS +vincible/I +vindicate/XSDVGN +vindication/M +vindicator/SM +vindictive/PY +vindictiveness/MS +vine/MGDS +vinegar/DMSG +vinegary +vineyard/SM +vino/MS +vinous +vintage/MRSDG +vintager/M +vintner/MS +vinyl/SM +viol/MSB +viola/SM +violable/I +violate/VNGXSD +violator/MS +violence/SM +violent/Y +violet/SM +violin/MS +violinist/SM +violist/MS +violoncellist/S +violoncello/MS +viper/MS +viperous +virago/M +viragoes +viral/Y +vireo/SM +virgin/SM +virginal/YS +virginity/SM +virgule/MS +virile +virility/MS +virologist/S +virology/SM +virtual/Y +virtue/SM +virtuosity/MS +virtuoso/MS +virtuosoes +virtuous/PY +virtuousness/SM +virulence/SM +virulent/Y +virus/MS +vis/MDSGV +visa/SGMD +visage/MSD +viscera +visceral/Y +viscid/Y +viscoelastic +viscoelasticity +viscometer/SM +viscose/MS +viscosity/MS +viscount/MS +viscountcy/MS +viscountess/SM +viscous/PY +viscousness/M +viscus/M +vise's +vise/CAXNGSD +viselike +visibility/ISM +visible/PI +visibly/I +vision's/A +vision/KMDGS +visionariness/M +visionary/PS +visit/GASD +visitable/U +visitant/SM +visitation/SM +visited/U +visitor/MS +visor/SMDG +vista/GSDM +visual/SY +visualization/AMS +visualize/SRDZG +visualized/U +visualizer/M +visualizes/A +vita/M +vitae +vital/SY +vitality/MS +vitalization/AMS +vitalize/ASDGC +vitamin/SM +vitiate/XGNSD +vitiation/M +viticulture/SM +viticulturist/S +vitreous/YSP +vitrifaction/S +vitrification/M +vitrify/XDSNG +vitrine/SM +vitriol/MDSG +vitriolic +vitro +vittles +vituperate/SDXVGN +vituperation/M +vituperative/Y +viva/DGS +vivace/S +vivacious/YP +vivaciousness/MS +vivacity/SM +vivaria +vivarium/MS +vivaxes +vive/Z +vivid/PTYR +vividness/SM +vivifier +vivify/NGASD +viviparous +vivisect/DGS +vivisection/MS +vivisectional +vivisectionist/SM +vivo +vixen/SM +vixenish/Y +viz +vizier/MS +vizor's +vocab/S +vocable/SM +vocabularian +vocabularianism +vocabulary/MS +vocal/SY +vocalic/S +vocalise's +vocalism/M +vocalist/MS +vocalization/SM +vocalize/ZGDRS +vocalized/U +vocalizer/M +vocation/AKMISF +vocational/Y +vocative/KYS +vociferate/NGXSD +vociferation/M +vociferous/YP +vociferousness/MS +vocoded +vocoder +vodka/MS +voe/S +vogue/GMSRD +vogueing +voguish +voice/IMGDS +voiceband +voiced/CU +voiceless/YP +voicelessness/SM +voicer/S +voices/C +voicing/C +void/C +voidable +voided +voider/M +voiding +voidness/M +voids +voile/MS +voilà +vol/GSD +volar +volatile/PS +volatileness/M +volatility/MS +volatilization/MS +volatilize/SDG +volcanic/S +volcanically +volcanism/M +volcano/M +volcanoes +vole/MS +volition/MS +volitional/Y +volitionality +volley/SMRDG +volleyball/MS +volleyer/M +volt/AMS +voltage/SM +voltaic +voltmeter/MS +volubility/S +voluble/P +volubly +volume/SDGM +volumetric +volumetrically +voluminous/PY +voluminousness/MS +voluntarily/I +voluntariness/MI +voluntarism/MS +voluntary/PS +volunteer/DMSG +voluptuary/SM +voluptuous/YP +voluptuousness/S +volute/S +vomit/GRDS +voodoo/GDMS +voodooism/S +voracious/YP +voraciousness/MS +voracity/MS +vortex/SM +vortices's +vorticity/M +votary/MS +vote's +vote/CSDG +voter/SM +votive/YP +vouch/SRDGZ +voucher/GMD +vouchsafe/SDG +vow/SMDRG +vowel/MS +vowelled +vowelling +vower/M +voyage/GMZJSRD +voyager/M +voyageur/SM +voyeur/MS +voyeurism/MS +voyeuristic +vs +vulcanization/SM +vulcanize/SDG +vulcanized/U +vulgar/TSYR +vulgarian/MS +vulgarism/MS +vulgarity/MS +vulgarization/S +vulgarize/GZSRD +vulnerability/SI +vulnerable/IP +vulnerably/I +vulpine +vulture/SM +vulturelike +vulturous +vulva/M +vulvae +vying +w/XTJGV +wackes +wackiness/MS +wacko/MS +wacky/RTP +wad/MDRZGS +wadded +wadding/SM +waddle/GRSD +wade/S +wader/M +wadi/SM +wafer/GSMD +waffle/GMZRSD +waft/SGRD +wafter/M +wag/DRZGS +wage/SM +waged/U +wager/GZMRD +wagged +waggery/MS +wagging +waggish/YP +waggishness/SM +waggle/SDG +waggly +wagon/SGZMRD +wagoner/M +wagtail/SM +waif/SGDM +wail/SGZRD +wailer/M +wain/GSDM +wainscot/SGJD +wainwright/SM +waist/GSRDM +waistband/MS +waistcoat/GDMS +waister/M +waistline/MS +wait/GSZJRD +waiter/DMG +waitpeople +waitperson/S +waitress/GMSD +waive/SRDGZ +waiver/MB +wake/MGDRSJ +wakeful/PY +wakefulness/MS +waken/SMRDG +waker/M +wakeup +wale/DRSMG +waling/M +walk/GZSBJRD +walkabout/M +walkaway/SM +walker/M +walkie +walkout/SM +walkover/SM +walkway/MS +wall/SGMRD +wallaby/MS +wallah/M +wallboard/MS +wallet/SM +walleye/MSD +wallflower/MS +wallop/RDSJG +walloper/M +walloping/M +wallow/RDSG +wallower/M +wallpaper/DMGS +wally/S +walnut/SM +walrus/SM +waltz/MRSDGZ +waltzer/M +wampum/SM +wan/PGSDY +wand/MRSZ +wander/JZGRD +wanderer/M +wanderlust/SM +wane/S +wangle/RSDGZ +wangler/M +wanna +wannabe/S +wanned +wanner +wanness/S +wannest +wanning +want/GRDSJ +wanted/U +wanter/M +wanton/PGSRDY +wantonness/S +wapiti/MS +war/GSMD +warble/GZRSD +warbler/M +warbonnet/S +ward/AGMRDS +warden/DMGS +warder/DMGS +wardrobe/MDSG +wardroom/MS +wards/I +wardship/M +ware/MS +warehouse/MGSRD +warehouseman/M +warfare/SM +warhead/MS +warhorse/SM +warily/U +wariness/MS +warinesses/U +warless +warlike +warlock/SM +warlord/MS +warm/YRDHPGZTS +warmblooded +warmed/A +warmer/M +warmhearted/PY +warmheartedness/SM +warmish +warmness/MS +warmonger/JGSM +warmongering/M +warms/A +warmth/M +warmths +warn/GRDJS +warned/U +warner/M +warning/YM +warp/MRDGS +warpaint +warpath/M +warpaths +warper/M +warplane/MS +warrant/GSMDR +warranted/U +warranter/M +warranty/SDGM +warred/M +warren/SZRM +warrener/M +warring/M +warrior/MS +wars/C +warship/MS +wart/MDS +warthog/S +wartime/SM +warty/RT +wary/URPT +was/S +wash/AGSD +washable/S +washbasin/SM +washboard/SM +washbowl/SM +washcloth/M +washcloths +washday/M +washed/U +washer/GDMS +washerwoman/M +washerwomen +washing/SM +washout/SM +washrag/SM +washroom/MS +washstand/SM +washtub/MS +washy/RT +wasn't +wasp/SM +waspish/PY +waspishness/SM +wassail/GMDS +wast/GZSRD +wastage/SM +waste/S +wastebasket/SM +wasteful/YP +wastefulness/S +wasteland/MS +wastepaper/MS +waster/DG +wastewater +wasting/Y +wastrel/MS +watch/JRSDGZB +watchable/U +watchband/SM +watchdog/SM +watchdogged +watchdogging +watched/U +watcher/M +watchful/PY +watchfulness/MS +watchmake/JRGZ +watchmaker/M +watchman/M +watchmen +watchpoints +watchtower/MS +watchword/MS +water/JGSMRD +waterbird/S +waterborne +watercolor/DMGS +watercolorist/SM +watercourse/SM +watercraft/M +watercress/SM +waterer/M +waterfall/SM +waterfowl/M +waterfront/SM +waterhole/S +wateriness/SM +watering/M +waterless +waterlily/S +waterline/S +waterlogged +waterloo +waterman/M +watermark/GSDM +watermelon/SM +watermill/S +waterproof/PGRDSJ +watershed/SM +waterside/MSR +watersider/M +waterspout/MS +watertight/P +watertightness/M +waterway/MS +waterwheel/S +waterworks/M +watery/PRT +watt/TMRS +wattage/SM +wattle/SDGM +wave/ZGDRS +waveband/MS +waveform/SM +wavefront/MS +waveguide/MS +wavelength/M +wavelengths +wavelet/SM +wavelike +wavenumber +waver/GZRD +wavering/YU +wavily +waviness/MS +wavy/SRTP +wax/MNDRSZG +waxer/M +waxiness/MS +waxwing/MS +waxwork/MS +waxy/PRT +way/MS +wayfarer/MS +wayfaring/S +waylaid +waylay/GRSZ +waylayer/M +wayleave/MS +waymarked +wayside/MS +wayward/YP +waywardness/S +we +we'd +we'll +we're +we've +weak/TXPYRN +weaken/ZGRD +weakener/M +weakfish/SM +weakish +weakliness/M +weakling/SM +weakly/RTP +weakness/MS +weal/MHS +wealth/M +wealthiness/MS +wealths +wealthy/PTR +wean/RDGS +weaner/M +weanling/M +weapon/GDMS +weaponless +weaponry/MS +wear/RBSJGZ +wearable/S +wearer/M +wearied/U +wearily +weariness/MS +wearing/Y +wearisome/YP +wearisomeness/M +weary/TGPRSD +wearying/Y +weasel/SGMDY +weather/MDRYJGS +weatherbeaten +weathercock/SDMG +weatherer/M +weathering/M +weatherize/GSD +weatherman/M +weathermen +weatherperson/S +weatherproof/SGPD +weatherstrip/S +weatherstripped +weatherstripping/S +weave/SRDGZ +weaver/M +weaves/A +weaving/A +web/SMR +webbed +webbing/MS +weber/M +webfeet +webfoot/M +website/S +wed/SA +wedded/A +wedder +wedding/SM +wedge/SDGM +wedgie/RST +wedlock/SM +wee/DRST +weed/SGMRDZ +weeder/M +weediness/M +weedkiller/M +weedless +weedy/TRP +weeing +week/SYM +weekday/MS +weekend/SDRMG +weekender/M +weekly/S +weeknight/SM +ween/SGD +weenie/M +weeny/RSMT +weep/SGZJRD +weeper/M +weepy/RST +weevil/MS +weft/SGMD +weigh/RDJG +weighed/UA +weigher/M +weighs/A +weight/JMSRDG +weighted/U +weighter/M +weightily +weightiness/SM +weighting/M +weightless/YP +weightlessness/SM +weightlifter/S +weightlifting/MS +weighty/TPR +weir/SDMG +weird/YRDPGTS +weirdie/SM +weirdness/MS +weirdo/SM +welcome/PRSDYG +welcomeness/M +welcoming/U +weld/SBJGZRD +welder/M +welfare/SM +welkin/SM +well/SGPD +wellbeing/M +wellhead/SM +wellington/S +wellness/MS +wellspring/SM +welsh/RSDGZ +welsher/M +welt/GZSMRD +welter/GD +welterweight/MS +wen/M +wench/GRSDM +wencher/M +wend/DSG +went +wept/U +were +weren't +werewolf/M +werewolves +werwolf's +west/RDGSM +westbound +wester/DYG +westerly/S +western/ZSR +westerner/M +westernization/MS +westernize/GSD +westernmost +westing/M +westward/S +wet/SPY +wetback/MS +wetland/S +wetness/MS +wettable +wetter/S +wettest +wetting +whack/GZRDS +whacker/M +whale/GSRDZM +whaleboat/MS +whalebone/SM +whaler/M +whaling/M +wham/MS +whammed +whamming/M +whammy/S +wharf/SGMD +wharves +what'd +what're +what/MS +whatchamacallit/MS +whatever +whatnot/MS +whatsoever +wheal/MS +wheat/NMXS +wheatgerm +whee/S +wheedle/ZDRSG +wheel/RDMJSGZ +wheelbarrow/GSDM +wheelbase/MS +wheelchair/MS +wheeler/M +wheelhouse/SM +wheelie/MS +wheeling/M +wheelwright/MS +wheeze/SDG +wheezily +wheeziness/SM +wheezy/PRT +whelk/MDS +whelm/DGS +whelp/DMGS +when/S +whence/S +whenever +whensoever +where'd +where're +where/MS +whereabout/S +whereas/S +whereat +whereby +wherefore/MS +wherein +whereof +whereon +wheresoever +whereto +whereupon +wherever +wherewith +wherewithal/SM +wherry/DSGM +whet/S +whether +whetstone/MS +whetted +whetting +whew/GSD +whey/MS +which +whichever +whiff/GSMD +whiffle/DRSG +whiffler/M +whiffletree/SM +whig/S +while/GSD +whilom +whilst +whim/SM +whimmed +whimming +whimper/DSG +whimsey's +whimsical/YP +whimsicality/MS +whimsy/TMDRS +whine/GZMSRD +whining/Y +whinny/GTDRS +whiny/RT +whip/SM +whipcord/SM +whiplash/SDMG +whipped +whipper/MS +whippersnapper/MS +whippet/MS +whipping/SM +whippletree/SM +whippoorwill/SM +whips/M +whipsaw/GDMS +whir/SY +whirl/RDGS +whirligig/MS +whirlpool/MS +whirlwind/MS +whirly/MS +whirlybird/MS +whirred +whirring +whisk/GZRDS +whisker/DM +whiskery +whiskey/SM +whisper/GRDJZS +whisperer/M +whispering/YM +whist/GDMS +whistle/DRSZG +whistleable +whistler/M +whistling/M +whit/SJGTXMRND +white/PYS +whitebait/M +whitecap/MS +whiteface/M +whitefish/SM +whitehead/S +whiten/JZDRG +whitener/M +whiteness/MS +whitening/M +whiteout/S +whitespace +whitetail/S +whitewall/SM +whitewash/GRSDM +whitewater +whitey/MS +whither/DGS +whitier +whitiest +whiting/M +whitish +whitter +whittle/JDRSZG +whittler/M +whiz +whizkid +whizzbang/S +whizzed +whizzes +whizzing +who'd +who'll +who're +who've +who/M +whoa/S +whodunit/SM +whoever +whole/SP +wholegrain +wholehearted/PY +wholeheartedness/MS +wholemeal +wholeness/S +wholesale/GZMSRD +wholesaler/M +wholesome/UYP +wholesomeness/USM +wholewheat +wholly +whom +whomever +whomsoever +whoop/SRDGZ +whoopee/S +whooper/M +whoosh/DSGM +whop +whopper/MS +whopping/S +whore/SDGM +whorehouse/SM +whoreish +whorish +whorl/SDM +whose +whoso +whosoever +why +whys +wick/GZRDMS +wicked/RYPT +wickedness/MS +wicker/M +wickerwork/MS +wicket/SM +wicketkeeper/SM +wicking/M +wide/RSYTP +widemouthed +widen/SGZRD +widener/M +wideness/S +widespread +widgeon's +widget/SM +widow/MRDSGZ +widower/M +widowhood/S +width/M +widths +widthwise +wield/GZRDS +wielder/M +wiener/SM +wienie/SM +wife/DSMYG +wifeless +wifely/RPT +wig/MS +wigeon/MS +wigged +wigging/M +wiggle/RSDGZ +wiggler/M +wiggly/RT +wight/SGDM +wiglet/S +wigmaker +wigwag/S +wigwagged +wigwagging +wigwam/MS +wild/SPGTYRD +wildcat/SM +wildcatted +wildcatter/MS +wildcatting +wildebeest/SM +wilder/P +wilderness/SM +wildfire/MS +wildflower/S +wildfowl/M +wilding/M +wildlife/M +wildness/MS +wile/DSMG +wilfulness's +wilily +wiliness/MS +will/SGJRD +willed/U +willer/M +willful/YP +willfulness/S +willies +willing/UYP +willinger +willingest +willingness's +willingness/US +williwaw/MS +willow/RDMSG +willower/M +willowy/TR +willpower/MS +wilt/DGS +wily/PTR +wimp/GSMD +wimpish +wimple/SDGM +wimpy/RT +win/ZGDRS +wince/SDG +winch/GRSDM +wincher/M +winchester/M +wind's +wind/USRZG +windbag/SM +windblown +windbreak/MZSR +windburn/GSMD +winded +winder/UM +windfall/SM +windflower/MS +windily +windiness/SM +winding/MS +windjammer/SM +windlass/GMSD +windless/YP +windmill/GDMS +window/DMGS +windowless +windowpane/SM +windowsill/SM +windpipe/SM +windproof +windrow/GDMS +winds/A +windscreen/MS +windshield/SM +windsock/MS +windstorm/MS +windsurf/GZJSRD +windswept +windup/MS +windward/SY +windy/TPR +wine/MS +wineglass/SM +winegrower/SM +winemake +winemaster +winery/MS +wineskin/M +wing/GZRDM +wingback/M +wingding/MS +wingeing +winger/M +wingless +winglike +wingman +wingmen +wingspan/SM +wingspread/MS +wingtip/S +wink/GZRDS +winker/M +winking/U +winkle/SDGM +winless +winnable +winner/MS +winning/SY +winnow/SZGRD +wino/MS +winsome/PRTY +winsomeness/SM +winter/SGRDYM +winterer/M +wintergreen/SM +winterize/GSD +wintertime/MS +wintriness/M +wintry/TPR +winy/RT +wipe/DRSZG +wiper/M +wire's +wire/UDA +wirehair/MS +wireless/MSDG +wireman/M +wiremen +wirer/M +wires/A +wiretap/MS +wiretapped +wiretapper/SM +wiretapping +wiriness/S +wiring/SM +wiry/RTP +wisdom/UM +wisdoms +wise/URTY +wiseacre/MS +wisecrack/GMRDS +wised +wisely/TR +wiseness +wisenheimer/M +wises +wish/GZSRD +wishbone/MS +wishful/PY +wishfulness/M +wishy +wising +wisp/MDGS +wispy/RT +wist/DGS +wisteria/SM +wistful/PY +wistfulness/MS +wit/PSM +witch/SDMG +witchcraft/SM +witchdoctor/S +witchery/MS +with/GSRDZ +withal +withdraw/RGS +withdrawal/MS +withdrawer/M +withdrawn/P +withdrawnness/M +withdrew +withe/M +wither/GDJ +withering/Y +withheld +withhold/SJGZR +withholder/M +within/S +without/S +withs +withstand/SG +withstood +witless/PY +witlessness/MS +witness/DSMG +witnessed/U +witted +witter/G +witticism/MS +wittily +wittiness/SM +witting/UY +wittings +witty/RTP +wive/GDS +wives/M +wiz's +wizard/MYS +wizardry/MS +wizen/D +wk/Y +woad/MS +wobble/GSRD +wobbler/M +wobbliness/S +wobbly/PRST +woe/PSM +woebegone/P +woeful/PY +woefuller +woefullest +woefulness/SM +wok/SMN +woke +wold/MS +wolf/RDMGS +wolfer/M +wolfhound/MS +wolfish/YP +wolfishness/M +wolfram/MS +wolverine/SM +wolves/M +woman/GSMYD +womanhood/MS +womanish +womanize/RSDZG +womanized/U +womanizer/M +womanizes/U +womankind/M +womanlike +womanliness/SM +womanly/PRT +womb/SDM +wombat/MS +women/MS +womenfolk/MS +won't +won/SG +wonder/GLRDMS +wonderer/M +wonderful/PY +wonderfulness/SM +wondering/Y +wonderland/SM +wonderment/SM +wondrous/YP +wondrousness/M +wonk/S +wonky/RT +wonned +wonning +wont/SGMD +wonted/PUY +wontedness/MU +woo/DRZGS +wood/SMNDG +woodbine/SM +woodblock/S +woodcarver/S +woodcarving/MS +woodchopper/SM +woodchuck/MS +woodcock/MS +woodcraft/MS +woodcut/SM +woodcutter/MS +woodcutting/MS +wooden/TPRY +woodenness/SM +woodgrain/G +woodhen +woodiness/MS +woodland/SRM +woodlice +woodlot/S +woodlouse/M +woodman/M +woodmen +woodpecker/SM +woodpile/SM +woodruff/M +woods/R +woodshed/SM +woodshedded +woodshedding +woodside +woodsman/M +woodsmen +woodsmoke +woodsy/TRP +woodwind/S +woodwork/SMRGZJ +woodworker/M +woodworking/M +woodworm/M +woody/TPSR +woodyard +woof/SRDMGZ +woofer/M +wool/SMYNDX +woolgather/RGJ +woolgatherer/M +woolgathering/M +woolliness/MS +woolly/RSPT +woozily +wooziness/MS +woozy/RTP +wop/MS! +word's +word/AGSJD +wordage/SM +wordbook/MS +wordily +wordiness/SM +wording/AM +wordless/Y +wordplay/SM +wordy/TPR +wore +work/GZJSRDMB +workability's +workability/U +workable/U +workableness/M +workably +workaday +workaholic/S +workaround/SM +workbench/MS +workbook/SM +workday/SM +worked/A +worker/M +workfare/S +workforce/S +workhorse/MS +workhouse/SM +working/M +workingman/M +workingmen +workingwoman/M +workingwomen +workload/SM +workman/MY +workmanlike +workmanship/MS +workmate/S +workmen/M +workout/SM +workpiece/SM +workplace/SM +workroom/MS +works/A +worksheet/S +workshop/MS +workspace/S +workstation/MS +worktable/SM +worktop/S +workup/S +workweek/SM +world/ZSYM +worldlier +worldliest +worldliness/USM +worldly/UP +worldwide +worm/SGMRD +wormer/M +wormhole/SM +wormwood/SM +wormy/RT +worn/U +worried/Y +worrier/M +worriment/MS +worrisome/YP +worry/ZGSRD +worrying/Y +worrywart/SM +worse/SR +worsen/GSD +worship/ZDRGS +worshiper/M +worshipful/YP +worshipfulness/M +worst/SGD +worsted/MS +wort/SM +worth/DG +worthily/U +worthiness/SM +worthinesses/U +worthless/PY +worthlessness/SM +worths +worthwhile/P +worthy/UTSRP +wost +wot +would've +would/S +wouldn't +wouldst +wound's +wound/AU +wounded/U +wounder +wounding +wounds +wove/A +woven/AU +wovens +wow/SDG +wpm +wrack/SGMD +wraith/M +wraiths +wrangle/GZDRS +wrangler/M +wrap/MS +wraparound/S +wrapped/U +wrapper/MS +wrapping/SM +wraps/U +wrasse/SM +wrath/GDM +wrathful/YP +wraths +wreak/SDG +wreath/GMDS +wreathe +wreaths +wreck/GZRDS +wreckage/MS +wrecker/M +wren/MS +wrench/MDSG +wrenching/Y +wrest/SRDG +wrester/M +wrestle/JGZDRS +wrestler/M +wrestling/M +wretch/MDS +wretched/TPYR +wretchedness/SM +wriggle/DRSGZ +wriggler/M +wriggly/RT +wright/MS +wring/GZRS +wringer/M +wrinkle/GMDS +wrinkled/U +wrinkly/RST +wrist/MS +wristband/SM +wristwatch/MS +writ/MRSBJGZ +writable/U +write/ASBRJG +writer/MA +writeup +writhe/SDG +writing/M +written/UA +wrong/PSGTYRD +wrongdoer/MS +wrongdoing/MS +wronger/M +wrongful/PY +wrongfulness/MS +wrongheaded/PY +wrongheadedness/MS +wrongness/MS +wrote/A +wroth +wrought/I +wrung +wry/DSGY +wryer +wryest +wryness/SM +wt +wurst/SM +wuss/S +wussy/TRS +x +xenon/SM +xenophobe/MS +xenophobia/SM +xenophobic +xerographic +xerography/MS +xerox/GSD +xi/M +xii +xiii +xis +xiv +xix +xterm/M +xv +xvi +xvii +xviii +xx +xylem/SM +xylene/M +xylophone/MS +xylophonist/S +y'all +y/F +ya +yacc/M +yacht/ZGJSDM +yachting/M +yachtsman +yachtsmen +yachtswoman/M +yachtswomen +yack's +yahoo/MS +yak/SM +yakked +yakking +yam/SM +yammer/RDZGS +yang/S +yank/GDS +yap/S +yapped +yapping +yard/SMDG +yardage/SM +yardarm/SM +yardman/M +yardmaster/S +yardmen +yardstick/SM +yarmulke/SM +yarn/SGDM +yarrow/MS +yaw/DSG +yawl/SGMD +yawn/GZSDR +yawner/M +yawning/Y +yd +ye/T +yea/S +yeah +yeahs +year/YMS +yearbook/SM +yearling/M +yearlong +yearly/S +yearn/JSGRD +yearner/M +yearning/MY +yeast/SGDM +yeastiness/M +yeasty/PTR +yecch +yegg/MS +yell/GSDR +yellow/TGPSRDM +yellowhammers +yellowish +yellowness/MS +yellowy +yelp/GSDR +yelper/M +yen/SM +yenned +yenning +yeoman/YM +yeomanry/MS +yeomen +yep/S +yes/S +yeshiva/SM +yessed +yessing +yesterday/MS +yesteryear/SM +yet +yeti/SM +yew/SM +yield/JGRDS +yielded/U +yielding/U +yikes +yin/S +yip/S +yipe/S +yipped +yippee/S +yipping +yo +yodel/SZRDG +yodeler/M +yoga/MS +yoghurt's +yogi/MS +yogurt/SM +yoke/DSMG +yoked/U +yokel/SM +yokes/U +yoking/U +yolk/DMS +yon +yonder +yore/MS +yorker/SM +you'd +you'll +you're +you've +you/SH +young/TRYP +youngish +youngster/MS +your/MS +yourself +yourselves +youth/SM +youthful/YP +youthfulness/SM +youths +yow +yowl/GSD +yr +yrs +ytterbium/MS +yttrium/SM +yuan/M +yucca/MS +yuck/GSD +yucky/RT +yuk/S +yukked +yukking +yule/MS +yuletide/MS +yum +yummy/TRS +yup/S +yuppie/SM +yurt/SM +z/TGJ +zag/S +zagging +zaniness/MS +zany/PDSRTG +zap/S +zapped +zapper/S +zapping +zeal/MS +zealot/MS +zealotry/MS +zealous/YP +zealousness/SM +zebra/MS +zebu/SM +zed/SM +zeitgeist/S +zenith/M +zeniths +zephyr/MS +zeppelin/SM +zero/SDHMG +zeroed/M +zeroing/M +zest/MDSG +zestful/YP +zestfulness/MS +zesty/RT +zeta/SM +zeugma/M +zig +zigged +zigging +zigzag/MS +zigzagged +zigzagger +zigzagging +zilch/S +zillion/MS +zinc/MS +zincked +zincking +zing/GZDRM +zingy/RT +zinnia/SM +zip/MS +zipped/U +zipper/GSDM +zipping/U +zippy/RT +zips/U +zircon/SM +zirconium/MS +zit/S +zither/SM +zloty/SM +zodiac/SM +zodiacal +zombi's +zombie/SM +zonal/Y +zone/MYDSRJG +zoned/A +zones/A +zoning/A +zonked +zoo/SM +zookeepers +zoological/Y +zoologist/SM +zoology/MS +zoom/DGS +zoophyte/SM +zoophytic +zounds/S +zucchini/SM +zwieback/MS +zydeco/S +zygote/SM +zygotic +zymurgy/S +Ångström/M +éclair/MS +éclat/MS +élan/M +émigré/S +épée/S +étude/MS diff --git a/docs/hunspell/libtorrent.dic b/docs/hunspell/libtorrent.dic new file mode 100644 index 0000000..86e36a8 --- /dev/null +++ b/docs/hunspell/libtorrent.dic @@ -0,0 +1,633 @@ +' +'s +libtorrent +API +APIs +ABI +SHA-1 +ed25519 +const +BEP +BEPs +bdecode +bdecoded +bencode +bencoding +bencoded +int64 +uint64 +enum +enums +struct +structs +bool +realloc +merkle +hpp +bittorrent +bitmask +bitmasks +SSL +asio +uTP +TCP +UDP +udp +IP +IPv4 +IPv6 +QoS +TOS +unchoke +unchoked +dict +kiB +MiB +GiB +DHT +LSD +adler32 +LRU +LRUs +UPnP +NAT +NATs +PMP +arvid +Arvid +Norberg +RTT +internet +TODO +UNC +plugin +plugins +symlink +symlinks +CRC32 +CRC +UTF +bitfield +RSS +rss +socks5 +socks4 +metadata +posix +downloaders +downloader +bitset +kB +hostname +indices +dht +lsd +noseed +BFpe +BFsd +i2p +async +uTorrent +pred +sha1 +sha512 +pread +preadv +pwrite +pwritev +queueing +readv +writev +ftruncate +iovec +uint8 +addr +iov +reannounce +PEM +pem +dh_params +outform +pex +trackerless +sig +ip +HTTP +URL +URLs +username +auth +idx +num +passphrase +UUID +UUIDs +uuid +performant +preformatted +SHA +buf +bufs +sizeof +params +ptr +msvc +mutex +eventfd +uint32 +HWND +IPs +CIDR +kademlia +userdata +dont +OR +ORed +Diffie +OpenSSL +openssl +libtorrent's +filesystem +filesystems +url +fs +io +ssl +errc +dh +dhparam +dhparams +0x01 +0x02 +0x04 +0x08 +http +failcount +superseeding +foo +baz +JSON +HTTPS +v4 +v6 +upnp +x509 +process' +crc32 +mtime +fallback +accessor +utf +str +bw +trackerid +timestamp +prioritisation +filehash +len +partfile +prepended +vec +dir +ut +ih +ec +cb +cid +mj +prio +src +'put' +'mtime' +'fingerprints' +'query' +'ro' +pre-partfile +pre +GCC +prioritization +nullptr +nothrow +precompute +recomputation +RPC +unchoking +ep +nid +crypto +URI +URIs +uri +infohashes +rw +holepunch +TLS +RC4 +Hellman +html +namespace +dn +pe +lt +tex +natpmp +cancelled +bitcoin +Jamfile +Jamfiles +Jamroot +NDEBUG +Solaris +BitTorrent +BitTyrant +macOS +int8 +int16 +int32 +int64 +uint8 +uint16 +uint32 +uint64 +Castagnoli +CRC32C +multicast +Linux +kqueue +epoll +LEDBAT +DNS +Nagle's +ACK +ACKed +getaddrinfo +ethernet +gnuplot +peerlist +MTU +ICMP +VPN +DSL +CAS +cas +IRC +PPPoE +cwnd +fullscreen +screenshot +prepend +RSA +DSA +curve25519 +nodes6 +nodes4 +serializer +github +Flattr +Wallin +Reimond +Retz +BEP5 +toolset +Wojciechowski +GTK +cmake +libsodium +nightcracker's +Siloti +Magnus +Jonsson +UmeÃ¥ +freenode +irc +hydri +BBv2 +DLL +gettime +toolsets +libgcrypt +LibTomCrypt +CommonCrypto +cygwin +gcc +iOS +memalign +valloc +malloc + +libcrypto +libssl +libeay32 +ssleay32 +libc +MinGW +config +xbt +tarball +cd +b2 +utp +stlport +IETF +BitSlug +BitCo +Tampere +CXX +gui +Multiprecision +gcrypt +peers' +wchar +ccmake +fPIC +Unix +ipv4 +ipv6 +reqq +homebrew +endian +gzip +KTorrent +Azureus +btih +der +Spek +darwin +dllimport +dllexport +stdlib +Wyzo +bjam +messages' +utorrent +zlib +MooPolice +LeechCraft +FDM +Folx +Tonido +Dumez +qBittorrent +sledgehammer999 +ntx86 +Tonidoplug +nodes2 +NAS +Tonido +localhost +yourip +declspec +gtkmm +btg +ncurses +Tvitty +Bubba +TVBlob +ISPs +ACKs +uplink's +Lince +hrktorrent +Trolltech +msg +donthave +png +DelCo +Torrent2Exe +ZyXEL +NSA +NSA220 +tcp +rlimit +screensaver +GPL +GPLv2 +routable +L1 +L2 +rst +org +DF +TVblob +FatRat +DAAP +fno +BLOBbox +GetRight +py +pyd +cmd +Strigeus +ludde +fpic +programmatically +unchokes +ethernet's +BitTorrent's +IPTPS10 +loopback +DLNA +EXE +Skype +lru +dll +exe +foobar +256ths +leechers +printability +podcasts +todo +0x10 +0x41727101980 +0x7fffffffffffffff +2410d4554d5ed856d69f426c38791673c59f4418 +E4F0B674 +0DFC +48BB +98A5 +2AA730BDB6D6 +0x0 +0x20 +2e +373ZDeQgQSQNuxdinNAPnQ63CRNn4iEXzg +BT +ID's +aio +btfd +d1 +d11 +de +e1 +impl +md11 +metadatai0ee +metadatai1e6 +passwd +pexi2ee1 +pi6881e1 +pimpl +pre1 +recv +requester's +seqi +seqi1e1 +txt +un +v12 +v2 +fuzzers +fuzzer +libFuzzer +clang's +prev +mmap +hash2 +infohash +v1 +Dreik's +ctx +unicode +peers6 +DNSName +SubjectAltName +SNI +httpseeds +Base16 +lsd +xt +netsh +GUID +NIC +tun0 +eth0 +eth1 +lan +NOATIME +INADDR +supportcrypt +setsockopt +OS +portmap +QBone +SNDBUFFER +RCVBUF +QBSS +DDoS +DoS +anonymization +Tribler +gzipped +processes' +versioning +cstdint +cloneable +inline +chrono +hunspell +online +dic +fallocate +strdup +istream +ostream +nonrouters +backoff +atime +wildcard +sk +OutIt +OutputIterator +nat +pmp +https +RemoteHost +ExternalPort +ret +pos +os +bt +cpp +tos +BP +qB +LT2060 +iocontrol +getname +getpeername +fastresume +InternetGatewayDevice +netmask +fe80 +vcpkg +leecher +6881l +NOTSENT +LOWAT +Dstatic +DOPENSSL +DBOOST +CANCELIO +fd +socketpair +fileno +SSRF +IDNA +toolchain +distutils +virtualenv +virtualenvs +pyenv +tox +args +destructors +fclose +fopen +cxxstd +uri +https +wolfSSL +wolfssl +sni +nginx +SIGINT +GNUTLS +libgnutls +0x200000 +golang +img +SetFileValidData +Qt5 +DownZemAll +LGPL +OSS +ccache +compileflags +cflags +cxxflags +linkflags +lto +SetEndOfFile +IDNA +MongoDB +SSRF +NoSQL +SafeCurl +OWASP +AWS +CryptoAPI +FuzzGen +archive +alloc +bt1 +ff45 +F1 +F2 +F3 +F4 +F5 +F6 +F7 +I1 +I2 +I3 diff --git a/docs/img/bitcoin.png b/docs/img/bitcoin.png new file mode 100644 index 0000000000000000000000000000000000000000..25e9f15ceb2a616525cdb6f08091b494f8908e81 GIT binary patch literal 2611 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
    #R#X0fJ7b{AkTJn== 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 gesbs>QD*ylh literal 0 HcmV?d00001 diff --git a/docs/img/hacking.diagram b/docs/img/hacking.diagram new file mode 100644 index 0000000..d9604ef --- /dev/null +++ b/docs/img/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/img/hash_distribution.png b/docs/img/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/img/ip_id_v4.png b/docs/img/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/img/logo-bw.svg b/docs/img/logo-bw.svg new file mode 100644 index 0000000..9bc0f2d --- /dev/null +++ b/docs/img/logo-bw.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/docs/img/logo-color-text-vertical.svg b/docs/img/logo-color-text-vertical.svg new file mode 100644 index 0000000..2184ffa --- /dev/null +++ b/docs/img/logo-color-text-vertical.svg @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/img/logo-color-text.png b/docs/img/logo-color-text.png new file mode 100644 index 0000000000000000000000000000000000000000..edffe3703395815b5976cc8ebbff6c0d0422eb85 GIT binary patch literal 17420 zcmX7v1ymK?7J%ulOP6#b-QC@d(yap0-6h?PbaO#Mx-T6f-JQ~1(r^6VtOWye7pytw z?B3swR9BTlMeES`>{+e zu6>Vj2Rv2KtHnzhTucNC9V=C*Y_V1X zFUSS57$79$CLSZfX-^zPGVXmYZBiW{A3w1|mN}gKc8vNK0g{mBgy};^=KLW2htRSs zBGXp7l!T)(n30Q01BAm)Rc*R^;Soy=!sAeo*p@9GLcv~Q8O8^Bsq}YzN%$c$|GSFfbo0`*vJ)J0VkIFh1j}RLhgsUz* z4KBXCnC37T~Qh!LJLR!&l@pGRR;DPfXE zK_z*fn=A@k_~FuNcR=54J4y=D5~>oBK~h4aE*(ii{YMr#lR`Fzo~m6UOJ-LhL^!Ng zkJJ40XwXCD#_tli1oi~BV0NQ_2c+s~2n;e}TegC(C8(VnO^>kTw>UOw4`%qC*d)HF z>s!KwkxKD}qYz0T5vG^p6bS1p>eOamnG$L6#ekCGmKZKvKrVR^cDU+T)n7dK6*}-l zfy;qSwH)fdz z1Lw19s#w*=7{bZhWu>;!kT2!- z&d!bt_l~GXz-y+M6x2e?4BSHu=-LcaAzy?hpX9HSH2D4CnCx32A=+M#W+?%WCV2(z zNhp**8`T@#u%CEMgyYigF}(R!GhC2`JTQX<^kgEGqM$X) zvN!pYBqTKPz?&!ApJ!#kPO)4R!))TOpc@{Oh`+$4KPVIUyp-HbO-Q<Ay;eAhn&z1V5Wl|53!F{G1Z15Wnr{1z4efZeOZlb4y&#wl76IM@NilQ`3;j|N0w+p?1njcM@8G zro%44nfdLLNOF;zE4@#P!_b6k2A0!+mF$%EpVV=xBU$0ieTYfKusg`?^Xc4xH477oJ;66C#_-6J0H}B&wUAohXe?^Yt zmjJbDNq2qf$MvbEJk4@CoXy{4z`%7;JtSo<=g&KonPG5_#!57sItZTpezaHVusSfl zZX{2~G7^bJ6QxPUu-6*}51bU%u2m>q%6<$e?XM)DnEPnvWA!KWWDo4sDl%6eBwm{d zacDQUTgO{mhqdAh#X8*t)|Z7%**q>!G@*9J{^|#1;DIHa7y1@XxiI};ZNuHDlTi9ULsl`C0XID4e@#ic z!yjCQwM!n8K_w|ch`7b^$u>hzFrC@9#?*U?+i=I)p4=3^Cm+I(B{qE4C314OB4r!E zEac-coqV*HHu(L6zG$9^AB)}j()fI?{RzpZ60tc4E~Amlb7frG?a5Ml5Cr2pO-d?YE4p9>*VxPgAzt8vH8 z-{V5S{qB_J)GK3W5m3Of$e*6m@PO<|{0*bXgIi9Uf4IW}WP0X%u=NC{h%a+o#uC+) zMNjtrx89bmVW%8@jbOTAJf3tX`!$~aRekpbvwkeKA+Jss*vk7FGuwMzY-3K6dkvfl zGw0&CBynU7ddb;X4Qz(e_xiFQ@>BFFtdpsGH4r>N1koum1NH216DH#fupITP@C#_Y%7$;6ZG5~-TH;v-2MH|l2Qb=x}eDUYms z(VnEX%E4tz!r{h7t$a-R_>w8$?Th#rM!^Ff1k*7Rv{9d`gA}Q5;=L&Re0|s5cX27s zpY~WH2#dhYlpQOC1bn3L>M1{J6sqC|cDa%deB}sW-^0h(F;00g#7Z(wvRTR9sY@GL zBo_;0W*rDJAB8E=4rOA%Ow-uK$8qoF-_G~!pF#H4>U52(?0oTAT4|2oXzo_* zkNs!s;H|FzsZf?9x-43*cR5qCWO7T%CBAj)*!Gr4x4Fl8t_9~$)ZVTj*pB&21On~% z5v_v{CY>X>dV#kVUzH!;5C0a3BUJy3cA;3H!pl=5F0GMz2=NksmjJg8Gao%#0Iaua zth=%T;Y^~0b%-Or_}zK*?uzWg-@hHFBb~4sm=j8Dpu`CQ{vcESD5t?cP9@-3IeIO! z_xrKczx!7S&vUNGfc0{Z%}*sB?CSaW|E@<2K!o)Egdbzc`8BIgom!yKzsdlOBc3pJ zkC7gusUv)QpmJZ=YRhGNS7W6#q5G)yVt2USJD>ZbYQ6j0HPg7&WwBCu^sLni2UWH~ zSHiL*I_8;w#O-N~CH%7f(sX@DlsXYt^mrD=GkR}c)zXva{m;6JpVlzxyMKi-QgD~` zt@kyGnLJ?L&@%*gyY6AwVBcsij!XXtx$Py@?0sQ{GeqM4!*5NZ%X5T^1*lTC2K+D! z=6VE$75EW8e-J&~DWrgdrnPzzsa9YW|$qeq%j|7koRCf8%HbH&^| z)|5%p+%+C{rJVYzUbQ!}DVPXri(roRWZz7Btr&Y}_n>a5R}>`D%MWP!2+!%gay>o` zfp=FGlZs|*)V^p8u?R8wad@yulyg&C2T-Rpf zn>CpzNycESjwQUiIo*<5Ip6zn#zM$3PZm5>9TVHvM}4}4-Hbqa63U2~HbrP_={{m- zjW?0csV$()gXdQGzQ%ZPX`tTI?%cuabuyZ!lSURT96Q|8bE0N(wl&gY-elq=sU8HU zCQ_)H;=#8r-;aJR|I4D-P1za&u7G*2@7p+EtLL?I zA*ZA)!rwGeeaUt|H&vsnjuMq3~!XHgm-W4ievTu(ll7 z>+M4`XX7WP?)7V=)5yE3g6H37&7an;jH-XMXYI}XLdr7bjrIAQk4tSm24m?+>VDU# zzM%(b$BUhe1ES~zVeI-&PpXd|gtr-?p@qRmR1FCJ3FRdeT+Z9iNsDJtm_28g7{*}=DQpj*2mS{ zpJM~Ej^n$YzO({xx$n6w8b++VZT{CwEW+074B-L5y!!ubgu{`tL#R*w01zG;Z= zZ%Lu){MHNWDW!|EjbTTQv>^It%1yb62I0rv5mxC`qRvBjGGjR99s&v=e1&&Tf$~~V zs~Nbyu>-wW47FfK;>I6ka%cGOMrHj1f&OU*;49Db9*q&EVTJ3?6=3Kfx3}l{bcV5I zWWiYX+rX(x*&VH>fatdf6@p{TzpcJ<|Omj1#eH zwO@cT#ywUFzed>*k zJ8DVQi0>pDD)z-`a<@1vaL9^F;BDisV`S)k>=RQffmJWl1Q{y4+{v&B; zEUUAQ)aX->RRzAzmYns9)^&|GD;f(|k}J_2t_N1rS!C(TU1VeiKJL?mKd6tSzv{Zq zaruz>9sYbwZbrR4NjPV^xDd_XD|S2Ur-WEiWbI^d*OJI9yW05qs&cYUihuon096|f zLu&pFIiWJZU4YEEsJz1@Ch=1-L-MWrF72At+^<&G=@MU$UQ+R-|k80#HQ*1PH*~4`SJ0iXOS(J z(35+eLw+j3ig6+3$BBVtd?)`0uRh^2z{ngQRhO`84!yLKnVJMtsBUEusbG* zWjLc=EADS^T`C`c0m21qZN9ahchjB}Nbe$9-S1DWbT1o-Q!t4+j!|0~cLpqUJUEKH z24=+R)wA|576Q@fD;?+)C@$EpYH=iIeq(dEO&yg(k9oN zHn!N8Fn%NxqD@O415W7%UM-D1%1q0dc1-GQ=>n-RL z6sv4Lu*Qj@mIKBKGXx!J{MKK&P~7dVHbgtZ>!_2!u`qGl?ssf~=g}0F?Pl7b$4usd zdcAi%Jht-!DcfXv6I*P=SNy-6wdWBNNsvlNS&)hJ z%#I1RXYjBM{H2AC@3`evC;3MR2Lub+c`iP^2)xhKHtYl&8ZuS1OqkX8Me`H3Ek@Oz zX-Die^SFiTh+R_F?m$!efy%9u#DJG}IQZvL;hm=t5N%WLm0Zm3gf$U7%%`0@F8pct zGF=EcIa*5TesNo_2jZ9%alfzA}Kg8GU8~xkJ!TQ?N9tes4Nxf!r z-=kgZ9deTKRWVAugl3kt$AE`%ST&1dru+;Y83woGw{h`hqPTaK`Se8s|JgUnd8A$M9F`4>1H$WJf^ zR~9E*6Q{*F_S5q`S~{wb*qXb0bGy<`s?FFh92_=8$=03>&Xzi&o;*Z`gZ>8DMY*Yo zM6FSM{n`;pLh_a3Nc!ig{rOUlN3(xC5UpM4-sAhXybPV3t%y)cQjtIHgafJ$?Oj(` zzi{$4ryvp_8&O>QQ2&ba*Jtw$N(|PWxl1kyaCwU(ipUeiQ5r6(7HEl%QwiBxnG^#kDuOZV@m^O24kdonG9w1>1y8Ii{sCcJD0@lw*Bl|HCGoY1~S9E9mp(+j=vO&h;p z+sTUQ=R!bIn#unHL0*q;fhu%RfzVuEf%Fq&>osA2UlEUgLPM!cDsv2V(2m1@B^Pri z=+efy{5X$9D!c-5xCJ87Zm%N_7I%CgenDL1rg%d5m}%t9s})@eGnxCzM($iyF{EUS zAuAV8{IkmSmmymk&erlPv;Kq&Y~iIx?$bz&uU|X~*cv+fi+spQIC{1!-t&K(&ONZC zct^h-I-ymP*1N-{ikg*mZfV(`4b?kJYm#%on!9;Cfdfg!*JTxJv{MggyHiuJ&1xw9 z(CWLVAh2(*w*D=oZ%q$hJFk@DDz?+papGs>M!`ni8HVsXGf^C~y=|@ICBlAq^`7#h z0T^ry*?3J8B98Z9LurNl(BgDUseM@>Jn>%=riyDsK0jNLk}C{pE!kHGRf!{NrO_-e zO?SE}yYV7~^*-L|Ev_|(k6Z3@SPhBj5Ncbv(fS|>6^txqYthRDR(*O!JA{GV95VRL z^E_}1g=~gu4bK@M5B0KHf*Ai!%Bd1G4xO8T%`zN9lj>kDkbBAh(;mnD5lBKunrzP9gL>=ZE>^gY4B5>p#_w4_QWMj3Lv~_>m)W}Tu z`QwG`CpA-L9e%lk_`WT3d(}VOPGQh!hXq6A$9`gI`=1Xygsp0^Bu(YXwaUH2<~tbhwR%Z_$>;qeSK&syRY~`*K2YO9ZtCf`58JxzY(O&DX~Q zrm;TC#%jFutfir%havPtwd#AC%( zw2t>orz*dhFNFVkn^pD_K^l5YV&tHXhjOE_9En=5Lr>v&$!FZiOR+N$v(M3s|5zNC zYg<;E)UwXM3qI*VaOy&OCX0UwIVIh(`~yY6?tD>hp2LViwq(;Il?YbOo9K~!FIW2k z#ogwyMPg^XM@^(XxW+`V$^m^bY)bwF(T~UFjCzP6rZXIs^DWILP6(+Z(vP>vXMV3^ z^HR@Xs{opx+@Q}IzByBChV#IIg`y{F-JCZQ9S*eWB-~N8PmZ=EFU%zzK zThy+rx{v8d%h1E323=2@oy((q8bavkg6pW=nu|aE@}=qbegLmF=gvpJzkifyn_{F` z72#VE4A?$#^TPO?U`RLOyGHGVBDEBlwPWf4K_e-XBp%eiC6K%+$vN!j_goRxH_Nh325?Br$s1U&ZkJH zR&9_>X8Zwtpoc}Y{+{Cgv;Rq78`-huvz7(_;;b({^L0D^8%NXpi2Bu122-HU?w#8k zQ&XIx1nIg2W#Jh7I${T^kP{xkqB8jfU2h+xGx+`saIknfXCDMhN8Sg=4tBeSP6%%% z2fVaTyydSQOXv*0_qAcZlGvo%w3E%X^bX{sNqI#Xy^s5Q;%(aS{T4Ul9_mFZR1Wz< zbu$Dqu&8i7Y(onBh| zAdP7TxfLm+yfc1ApJ%(TyD5H@|BRxOQvjaYao`Xmx+H;Ax4FE;q@x=2&$!}TKwa1@ z>iAPyy|;j9p3vOjY&u(HOD1)<4mq*}`$am(%Jn23so2*0+2QUfL23fo%;{4k!w?sr zjrPsye}&^+dx+;mp^3;*?zVLyu|KJ|VKzSAnFoo%lMN0c=1d$g&r+Ry@qa`Dd#6tg zITNd|&-x-(&*i>H@Q3n6Y=@Lv?b^@;t~Ihy4~f`+G3H^u+Q}sb^I749w{u zupZn@k6K5N&dSo8oPBPU99pwn7PxvWf*`77U5z<$1qP5zPYCSdlZ9AtkdSoWLS2M2 zs6w9)$)Edo>9kV5`EH?+MSwlhzyv5QJ!Vt;U`4Ciq(S|nwa=akcQ=ijQA!pKJ#CxT z!Ub(wYbkJ1j2O69r{sPWqRZRis^5+JFF7~SvDE0HkF*=@3ZGnpTZl<+jXx1TP&?In zRrw!yRm4k5u#JS8J^ev;M0X^2zslppP~n3zf$~Q)3=gFnb)NBKW|wX)=;S z@|ocU(gu3XwdK=;pAF7R8I`N8Tp_8$(V*-=A|*TwDRuKOKe=&cxzO6=!qt7FBd~#e zi;GQVYR(!ACbB1Gg@{rwSnSlC3CH>8!d6LtG7sLsbm{RR$p2JSi0v5CN9QB+Ifh4l zds#+1z&kMHG6V=X=eGJ5)^kqia^tkH87M6!m$2(aM##K1MIQFnhyp2SI&huX;tKbo z{>;pD8!^(peF0<9S?GNxmzDlXzJl^xoZ#hzZvIO6-Qgr@$JR-j6}BtH{Q!J=P2~xuAwM`eT34ToO@@J+?oNuYOl?P&r-Xn@YNd1!(vX(V()&@ZqT9! z`V8t7S?Dr0jCC_?7-XAN`bTUC1V7ifA$KXrfpz-t=3HiwQWs zA$OKov5Xt6*sE0dNqs6R@@!%y^yB8aOhx_IQaPtWU3vj}G)!?z0o~tDocOTywcjq6 zH3rSz&n5`1`LX5>Fpp7xL4q+HsH?DRf^m3#N_z9?&wIK|aN)GjjJSw%SxmOWnddhd z-o=Fyt#sUC1S{sC`WngwV5-heD?u6JO~5lEa8;eP7D{Kc@Adq&OX};=W&^f#7a#qX z_xFpFYg}##X&QqY#z8Mmp~rjTy>>U?KRMEL8@jru2y)^h0b41TT~V z_2`HChNft2f(uMLR;>tbR|=uENa|ZPwJ{d}8&kmEF%X4U;3dBVz0mM0*C! z8!#{E%$a#m9y3nnc*eMz+1~Zn!BoaP-})ecHvI~#wuf!zzv?x!KffTE=Cv9wtc3fC zO$)>cAZ>!dO0qWg{gx(bI@e)!Ly6fh_% z&V&dxCvS)GNYDgrnBwq>mQ6N&aq_!TGhS5SKZL85v_!txsFl6YTm1V{s&Xzhe{8shjkVfw`^wa9IMDE=^Y(S)+Xk16$Q>{><2~4wDXN@z9;8&W!c>eOxq9<@hea>oR#!;kFF21F;EuQ}quL#A$&}lI!Yx zVR#AYE~jM3YCebx_X}DmO~8l^d2_F4m#Gl6XFMSHeg=wnfslOfKM~!iVODAUT_B!qOpegy>>!wxdujti}pk`I-FcT>7i_9&^n0vLFJkC)!`h zf$d*;Vhv}9Cx{_0FSBiUgtTF?V;f%C`>^d-Z`it~Zmxh(0OW3QD)!`&kpuPgZ1hA-`j& zbe8iG`}Csc`|0uQlmoP&<}``iTOW-3ce2vleicE~SZ&FN%qB4#SqR2$9gxmJdO4hNt0on~z=-nBQtI~nwq?fdsb=PZ-x~9c zm3_advAO=sp$L4VNUo(eGwR5B)6@Hld42jlX+il(g^mcFHN(993$M%u(9lsu5}fxy zh)-H}w)Uxb`nSR%<|=TVP_3aI{@mITehP{WgHWBMFLzA~!-Ku{&ZvmeIdHfGU#k|h zsvG#B=^)idrI$%@S?-wemfkI2*mC1`D$?*&ufju{XAi&k-ehN%g`Plu-=1NLp}Ca@N&XNfCL1D34Vsm4Wj-&ZG3!`1 zz;L&IqG^6NZUv+Ju;{aD0CA%COLBWBqtl?Hqfq<}HR07&Oo2^d5-uDyN}KQH(4Q)k zcuyvXN)eX!+h*Qs=U0g7O=;9`q^y^ zDM*%%Q8JS--HEDWeyzVEO+#oZ7m%ggFYe*0F+*=`#fDCE;h%fVltzCLxnni;oyk(> zCTrEP#rbyVjt*`!n4``;nIN8)30>FTV87KGi`B$tD4<^g$__I}*IdxyKud1hI6JMY zGn|B=Z+0<7>!cPUabr-2e@9iZIK25fI!+O_w56xUP}6 zT^$TFTPLF3q?u?ByMD&8a#2k0PoX>{;lYd#m##x6&j;4HYt`V9SLr-A4q7c4wlz7M ztJ-`dAv18F4q7qd;zGb8s0S3zsPUcLUhX{3?AuL6T?KBdj7|9AvA#(1LC$(H8RK)%v)^}`=MY$2a8R?;{P4kv$52tH%@2H#J7NE#DT(QhhI zfiH94pZtPWjPSFLJlFf=Xhp7 zsu2%X*Sso91M~Fi);-Clm1G0q3EPlk}mVGKDZC;*$ZO?HW>TawfIuF*G znN;H|)I@Gx)LzfGlU@RfzTuRHTS0MNx|tCdAH+7s$Q0`Z@qY`;JDX_xqZ&7_fm3b0 zAM2K#c&MWU7rR|$a_;Z!9ho)qvipv{t9S>ykCE_Ue!t|$F0-uEv z_+ouz!L=W2+iMJAib;>@-c~*HR`vQZhu{xv&ndJuZ%7qA^aA9ZICE$=7t5~!<^A5a zZt7k4L;XuXd$%2#Ehab64M(W1YRv}9Q@P$i`6I)aJ9a^c@4|aumbKFO8`!+x(P@v$ zSn!6^Xr!9!D?uRx2fea5sdndrgu9w+o+>SnT+x@$Cv+EXK-FFzr~5Ys>Z%cj6>GG@ zWdI(2`?juM#P_ImLYxT?x_a2<6N={p8V_@msaD}Gm_-w%1BYXx+EBP)Vss@nIPX1d z?Qu7`WNIvm!`-0IL97S)cx~k<0Q~@!Xv@=gS{7Ov#NQ>AOi+Pk43m*KOB(8XK?h$v z_YfS6EXoVt0>F-=*}#KMF%m(jIXwbW?$ObC6|+E&4kpuZ#@SC#vc)X42dWr@+KSTn zbc-v~(c(PMuxHjLHwYr$n7-m=mNtqVYn9S9X&a{JWv-Iy7pzUjV0Tm7(A^!~+3(&_ z_(6hmjEgfIl@V$_>y$Ej^fSoI-WZnj-qhdHy71Re{F#r(N!*GI^_`%ju2y^Y57lmM z!hWqzy8y{yZ&R*)$NHtWZVMp?(0G@1D}Ic*9!7~!O2mU>Za&3hcS1eO#XYxMnRFTjcRCV`bSQs$l( zrc<2`b#%V;@)+v43}}?NsM%CKGD%LtT}B!QR?Q*qPco2JESZyzOsB~ zwj+3D$g>9V{5Rus^*dD9UA3`AC_^xqdk;jll&ut=?C+rR1GG3#c_$|2f(I0AMrVQLnxIe<%9S&g`$`?t`=X zKD4prHbgYHHDCCypxT7m)<`h8Ss)dH*ZZEL+S{*uX-$f4vGl77G07iHnMc92JDMz~ zo=B!1I5uX-I%4tIJ`x}jE@V8Uwj|jb!wpEE7N^0weddPV$j((?SEDZR3o17 zLD1(~hAUvI`sl6AYczmw{K)X&!f6!vd-N$D!lrRZx@Cy)-R$o~5N1NR;Lev#CwygB z&28lVTFiKI(;=P(8cnq4`f_C$U8V-Bp^aa@T&XY~j>D-SvObhR0J6dOk^R@hR`_6G zIe~T5Ntw`K_Y_ZigZT7Czje2IDjup(Y0H=? zacHcmfbdv&F5K4VDot?RM7*9Hl3ThuWpoNHvT z6r=q$IC*&_WJgD`O5SP--)rQ<3hSeFtATwb`-^u$bTjMK8W&zs@k(>|GCX8{vb~R4 zCzPabZ3hmF4$1^-?%!(MIl(1QK`WT<=QZbK2Zh`p-8~7WU+92_4Fu?-Yott@GV0A4 z_uMzV2GSeknd@FE6I-Br9&rHX7}!qn>mWDb=_^9=tUG8)I!m1oiNvq@m)2eFw}=4W zqH4Rau33hpM=CrgC*!%({d=PQ-Fm1_-|ojqUUTL)k?0>K97L7h-$=`JU7cxl#aDxK zCX@BIu!${%;-SSVm{wBnAr34LoWP6b!Zw-TZnkD1`{I%C)grb(F%QI@yI`Mqky9fc z)7Dw3*T9Xl=D(TQ!FT?uy{C`F%d#Ls#5CaAb8@3&!kew=86EJLn)S6oJ=m}ypnA}( z5`ygM-yW%&7BM-P5!m`=PAI9gbmM2BC=YdMh4cs`BnIqr59hyFQz)w6I-Ot?y`R`h zH{H*Cr#}?SeV6kUstfsfpf+_QuC&whsI%Ze3bq3Jqo6xFmuDHz@13VyOOL!Y4bP*x zT3=`aU1La45bSf?9^Z|XJHV=KF1OickXG#MNgaQv?-uxu`AaK)iMHNrBISD|ym^^@ zZdHCu$|BU**MTQdc}jsK<6c$khjOT|khGx=lon8V-hu771XQ~gaplD#%CkfMPHwM} z`MsmI-+>0lqJjmoE`?#BkyTFK!;?mpp)-L@yCzAUP?Yao8nsg$&W!%hmVcEZd+X8k z8{v+gnpppG5IMqYW-s;CyAzCEgiWtyPTj z*$369Gmga}>RO4S>c_dUnFQi3Dg|B@X(Li4Nj@}z$yOIy!_R`oPcS5~FwjUh^;o*c zOF57;pw7$N(*U%(rr|BO>|@?w->*`>)`Hg@AnB327ajaI9{Zs^yzrkVI{(~3Ybzv> zs)4I#_8C1F3f)`mF@YE}4{XUWIiqj)Yul?Wbbh5m>&0;{$!kve;I6edyYQWM z_A#S)d;V+J9%G2w223mpuX8+n1OQW@6`!zX&%f=c0stb~PLx0p`!Evsw7Agikb(6< z+Y3aV`$~CKbX%5EkR{<1Jz{zt#TMJDh z=Yms9o%pH^pLy;jwIeO3EtqX&VnU1U|Mdui)t7iR(Vyt~ z;T1=4dMAi6_ltu}hEYvv61ZE!s1NqiKv^mgtubbAZjYz302`I~CiWuQ<2xKNJSW1)jHN?=Pj; z21={>{=PPmU%gf|bJFgNA6Lj36~~F?-bwu9{Q!6KXA>eJy<5CLel=UfuRb;n9sq!r z2lm@6kd#43?5W7grEKjAKjCOLVc|C8V;<5(z@OlW_`~N){0$Mpjt1g(a5ugu!F*>! zR$t^ULao8%>7ve^7HQq`I8rbDa3_yEFXH3h<JGH6UFWZ_Ymb&M zRT~+*Jw*iIXN%?sFRikF_E*JLFR+&Uc<$E1l+lrgSKOItzMYwS_Ytvj1-%s6B!127 z2WV;yoMit*C~vN}UGs;(G^9=lYS$drz3}%y-5K2VPv;RM?jNnNF*i`%khYES0pb9* zfi|rL^HU-T47J@Tyj7Ax&%JU&5eV#+f6T^DK<4E1{E%l(D%cbz&i1$>rqWfdQZgZBxw_oAAh|v3^#`C;6 z_UcB>5FAK8zuWuvNTRRX85)Q@2@GEZ8Dajs6Xya5u&-ZRBL5vXJM{Uwm1 z0XCtd`BEfXOOSqLKEHNxW}|oJ$HYJS?tAz#94fX0Jty(rHZn zEvHlHVQJiCah8?(rfjUAfr}$1!!5XDfn$7q!sxHw&}^B|d^JSK7bUs108ih6ZU7~0 zd`!)KK*)9a;p%0>V&+55CQ+CMDV}tyIA_mVw6mHnx!;4TA+o7bh|Mx~&!HN{XRAGI zp)DJ_83{@K#*ZT5^e5cmCiD1s7*WeN)WN`%!KL0D8qYUW{x9@5&Q$^JTaJybs#O#t zs)QDq{r1aAW7pV@7fFZMRT5okeQskTtL^SpkS{!g#}^%as5d(tX>Si&Oorr6J>J%0)J#4fLFu#$b8PCq5-?C1R5{npI)#waNKJvL-+NzclEl(5V3U|>C( z_o--2Za$lF*MIeHJ8wsTOmLZ-Hn?=gm27cBt>($BXW@Dy@5!Jo#3-}Xw83MM8tZ)j zOiB0MOvbyd_t;}G8jRt;P|#T21?QZ+Y3+yj_L~?^OCt^(Fh(P_ zZk-e}A$1;#k}bYa*hlhKSGIqf7H0%pZ&(D*pON{pRseTBA?Jt8HR@@SRBuVmh(g%h zMIh1P#@j*$-9xv@8$CdJg)C5TqSy^Uk>skKsnN^?b!VIP0Jtu%_mi0A0iATtGE4U+wh&2{AD{nWVJ2r@8S}66peL+E3K?wKDPFbCZV|FAXh#oO`^45( zvL!@cIGUm+fnXK4N+)m9Y+>8nh7qySI%ApJPw@)yno`0kTs`iqb$@0&2cs=KrS9G(yCt{5OG zv;InlnDQHqh_pD-H=ikmHjuXKI$yN@I@!iv4Er6yB+e`xnyS>57ND@A_6&?_kQ4GlfPf7s z`Zgwh9B0gt@@vQiN=pCI(tnRT;BKcz-ZPj$iq&b`pYCcaj?vc-mScL<@p7ey>zs6> zsHgyv8Yns?Uyl-Sw%^!+spdj+*r|fkLHtqH9_UhUQV>vhEl#1Z{`dzOEr2G2sjlvf z`ZtW#5#^GJTTN=+!DlY-iR`~|$t1RR$z}Y-L`_kP8U-^%T``n`6-f;CfkaOE&BXY* zo9^-T5g0|xW=C1pn%^*z`lyLbalx_4Tp-@^$^YeHHg!J!fQ6@p>tDg+>Zs%&_bx5i z<8UuB-WJkbBsMnz6e{1>{LFKsZU4t-cldr1`K-d#zH67J$7TWW+*GQTi*eEkCp5V? zNdUZkDDOI*c#_XNCF9T*FD+V6kv_ z17HI1&<9~qiCOc(E!NuU25KUPJDjxh!vm?&8AhDnVJ~5Y*B;RvTQN6+V3gf@$ygr&J zYHQ&=mli!>B4LLf4cP!A~#s1 z0}jaxgL0xLVu_6ks6A?mA&2)puO$WK9{2zQCOa;g{B+%IgFauNM?tS264az&G;CDY ze2+$nQNCKJXT)%uVKyqn+`Ox;*r!$v1q}3F0p}=;6)s{_q#y`}E`cY4zLN&%YP2Pb zxY7xJR%@8aJSha`FsJLQ%Pb-}KolAH!MP!zC1$F;+u0|D$DX|UI15%gsVOog6~NH?NJw!a?hF3W55|_EOb8A{q57%9 z9x(s?ETHzi`j{B&TLAsDH!uL?t4bT!%Biv-C6cnPkBgWKD&DxBj>foshcc85nNzKIV^k90(xjKFFoKP)THvi@z6o1RNh}m ziMSW08WMDtTgC45F@6m_&{bbSJ+)GS|#sQ5Ovo^d{($bx$GRGQ-$Y7TFtCYZO zw9}R=SxpvNiSH%kx& zU6)5Zx0hEt<%ko9rY`uqNIo6@lQ$-(-uJ6Bx+(}9gm4M<(!25SqG--d$1N$cqvcH1 zB^{rz6UA{Y#uV`Az+{KoNtRTqf>!Y16fd%4T(Y~zR96wysx=h8JMT6`82zq*v1%_N zr(UDt(tysXwrQ|Lj4Gm6UPOmU0=%p;J1LSDF=JUrdD6Bc(Uv%&1u4k{2P=%#1vVb= zriumyiHd`l(@oYfAOV_;vY3qX$YGo~ej}>jo~`cc`>^nTOaI?I=9tLeo_T-9EX$dW zEm@B11SWpx*yOX|o`kzoUPs1;jztBA8w(64I!sw0eW>t`Vy0KhlepI(s^nM6&3iua z)1)PnI47-SSsB9g_0j?bC1$rXYJ!zV65fd??p1zwAVJ~L_q-kDN$-2Wfqc%B|I&=F z^MeJ?Bs9c3OpsLC6f#L_J2c;sKU!ORmy+36?U7N*;sm$6Sc@eXXkB{l5420Q}H~|MD9SAC{cve#;$r=!I&DYeY#(Vo9o1a#1Rf zVlXl=GSD?J(={*+F*LCcptHiBg*08-nxG qO3D+9QW?t2%k?tzvWt@w3sUv+i_&MmvylQSV(@hJb6Mw<&;$VHAL=gv literal 0 HcmV?d00001 diff --git a/docs/img/logo-color-text.svg b/docs/img/logo-color-text.svg new file mode 100644 index 0000000..f510e58 --- /dev/null +++ b/docs/img/logo-color-text.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + libtorrent + + + + + + + + + diff --git a/docs/img/logo-color.svg b/docs/img/logo-color.svg new file mode 100644 index 0000000..d07360c --- /dev/null +++ b/docs/img/logo-color.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/docs/img/logo.svg b/docs/img/logo.svg new file mode 100644 index 0000000..589ee7a --- /dev/null +++ b/docs/img/logo.svg @@ -0,0 +1,1309 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/img/our_delay_base.png b/docs/img/our_delay_base.png new file mode 100644 index 0000000000000000000000000000000000000000..d14ad9ed86ad21a5fed783be6a3beecd1f9c1f96 GIT binary patch literal 34999 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 C)oWA$ literal 0 HcmV?d00001 diff --git a/docs/img/read_disk_buffers.diagram b/docs/img/read_disk_buffers.diagram new file mode 100644 index 0000000..1d04a25 --- /dev/null +++ b/docs/img/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/img/screenshot.png b/docs/img/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= zn-4^$AX6QQ!P3GZ>>W^lxYj*8CvBg}eXU54@grAD%P-+b1tX=9mW@aD1VRhX)>V{0 z>y@exs86$Tkwo62-6NQr9O!4!uK2l?a@Od?BkVYlTkumoMvsNEXDeaK@LHohHOP0i}o$ zf$#zJu9{SBZ>qyWDkCFfjgka`Kg20Lb$QX;)bv{Ci!P>Ii8Oe;dIbWUwJRhU7%;uK z1qIo;xvR;r-GERClpfL;GPvp)6BNXmg1Qf5DF&$s{`MLx0yKLALqqh@?FwpY!;e0~ zN^kXNrU8f}n`FoFth#OmM!uG@G3=s|fZ4`F=RY%eY;2_`Gzhfh{1)2k*)u^c&1Y1R zZFrFi%2F>xd?62I28^Nfbadx}WPX5q7>|&UnO*5DO;x7p2GsK@sF{T4luxjTZ;9LmGS1h08%W#zcXlH#%Db%+|i++%~K3$&z z>p3x;2!ISGfL9t`8ZXA}2-*}Wjow>3yY;PIT@ij~?;~UQ$8q& z#aVbB)#<4u`vibLONK!@O7Se;Lq3qnpk)?cUtb??G8X`L1+kMv*}qmed#Z3cNfodO zF+>J}Ok)OibqB~J5<#0Vu*C`iMhK~m)bhq-^_~3n-A!~^X^hVapdx1H;D80JY zHYwpKder)#kK3u%UheonRtL8b9lSGQ#oYNou%jvkQ zzgBT3$a=Sgtanyc*^8TC3$?Pc`T!Oum9(19Q?UIWuBlr#K> zqDC0QgNKyYv@uX;u4k`)c)`xb<_kXtc8j0zAlnNa4$q!FTJB5#qWev16I0sax0ehd z8Td>Qk&(&T*>Qm=k07JLgSy?XDk;A8$JjTM#J0cc`=qf=_R~{!WjVYxbZ7=XGIj3~ zs(aN)hfBYqdEYT~xL`bM*K_Tj?q1Z$$oMJIl4!k}r(RV`QHwfQCNTcU_=RF!)L0*N zsrz11nTkt#$44$*wC-B~3)Hhql6_g2!n&pDsRKvAzxfG1lj8UPA6_=M4 zT{23pG>SIIXY*@E(e(>mCiT$|#95&{YKP-LRo^4sWVhoFt27xMCqB)1I-5Uxv-bN# z3X`D*0)7jAf)IkQm%nC6sP3EAIFOsUh_c(y&b|$1i;o)FDg53uK@=HuoP8lZmbA^9c*4kq(N>4*(%IZl>ZD*X-V$M!-Qw}n9GVuI zU%bA^$G(;FuBi}X%D;d9Q0SE=iHf+xwtBRl|F}}=(6|st&&@dLt7MoIt*zZp`?WO*9CFUDJ)yJ zBruwhb`bScS%-DKRho|wK}Af*r}6Z-6Z9WGoLN{vg8TsA0YpBRVTH3QIKb>^g5XHi z4*2hgjs-wlz-KimD+{61NMQ(HWM-m2Pk;eu)__u4ew zT^ARX_)VLUufB9X2fm0s5I&K`#e$ukR@k)_5#k5Kk&R8|pETG9y$uG>z9z&)PD~_1 zSL+EaEprXMIC)D;hR2UHDJ8t<4iBeIo-E2sO9$zAA)%Oy{QSEP4lQAwX9%9T`Xots zlbKj9%SzQl9oXYlT21+8`pA%bSvCxO+tYDN)gGNJ;@-D!-gG%JGBN_K3*)W8rV_N9 zs=}H;F)3eD6R16`k1R(?RJ$MS@;xiB2&?bqO;{f{6gd45FW_0DX|QMZQh%l@2^}3$ z1OfP3b13lhpez&NIEE!vaM!|;{d>dQ)#^(Ie-n99)C8uAyEa(pE?+i*gI2mmSF!cD zem7lw+%43x7+^uxqtKa!t8U)9HD0$7J{xem5ug!dHQ^8b6Ymp&EX_d7E_YnGcmhfV z7@$@ci12blwJR&rn~Qm>Xs{}6?d{2Ok|BV~LP&xlo(G6_Z?fa0j>lGvX$^b;_cvt< zEu9Dr5h1*3YW6SQ`Unp^!2%5~fkcQLf(z@ZjSUj;q&iJ_2XKfmzlAp(L!|t3NS_Ow zOF~FMz^vGAV@>`F+4K^-$!z8Zi1{(yycr7H`}JtWQ79-w@_*nJ1-mdz9dcb58WMk9 ztQFsf$8l|-gDff>L>mVOuR=k+PwlgrQwUKG!q;h^K1o>|?=JYDyJ5*iTEi}1yY&L% z-5FxzV?ldNU8u83Dmx_?NB0VwTI&P*PNxUjHd4e2eD7s4> ziu)6;Oj2fMCbBrov7H^icpm+L2>PMp5_wOB6FRv$VI=oh*Fy!;MAOW<+LwFoLHnP2@#n7>3d%Pft+K3BSaM!tP)nL+sYEdfxupk{ zDo*Lv&ciyE_F2~79ikXJ2Rq>edn?R2ZCi<1v7DF$kAyVTim^HCLgp=8mpkdD6~lHBH5J3B@A)Vuqho#Ca?L2baL14&gOE3q6xpX zwOBTXB^1Ag{-L;2YQjl8pGU~i6ut8JcvLc^IAz8o=6e&QWug>cR-UhPOm;~s+*FiZ zNac545Tq&lhZmrI!4ZZj2$vr`BRjuRB=m+SCuv|cP!EE=7y-G$U@AY~KHhKh7<JV5ap#Pzc4yjy&H*>i8 zR$*Za3cMmv`rZufYGdB{q0cFZ=|G{z-p}1$jF3{qNjW3$MW*9 z+1Z0>?gp4QerO12ahR#s`}AqRm}+TZ;pETF&HO>yzX?AW+tRoOyWsOrN_{QioGZ1>A~!G@~xtCulK#p>A1-^kmJpraQ4qv#kk=kiqD_B-){nUJF?B3YHr9c zL~`>UZc-Tq_R=}Xu&1UnLPK-?*qEMm5{9W8SyPHQRhcE4#2uYP$YetLM}cIFHy8~v%6fp4KNfuDCdXT2`R4ZFH-WFtpF0+d z7`tC=&e?imK(s2#F};M&|DtyAMKe#c>Wz`lzN=AK( zDZa#>YCJpt+2OMqFa6BdAx|As-hNLHK7~)!4QcnXm}Y;5?|l2Vu=6WM-fQL_WcFec zC(sn1?+fPy-8x`AbSX{?qf{ltzdwKR>{Z9xa<9W#oYrJuxanUSEy#G;a+CJxt1(Zq zQB~a~_tjK7$zS8)sf^7LTeD|~WkXW3vw{fwG1{hJx%tO<<>mItg7ccHpJ`U7$ zTlCmpOdN>1e8~+UDsV5Dot@=@>jKL=hn-)&(_|4j5c(@vj7iR)a!-Jfc!Ry1jooO$ zSqLlF9Bqg~F)fU~60X@|e~(%uZvlR|7|qw&(-WZ_FBBOaeGfuApnF1S$6EQ!&IO!9 z@5Dq1kVJrG0tmym(a}xt*?`sSEm3U}yz?oe74L!TceCb*3uFSYNJ`Ib07-GG+Ow^- z^$u5zCLHUaWl}(vZ8xnb=-@!%)$6<`V*>2b0UJ82xc45eYrlLAjG8{H%efyec(JI~ z{D`0zJ+bSJgma)Riin!JLv*vM0cwAda1JCf#>T{IutPN5`LR_PN7{ERhBE0IUoFD- z@Mx!|^7$Q1$uu(znkK45J@+sj*0Ni67N_R9$7ATG!YWT^7r?v`9)yf~Di--(g|9yGHFIHo{9Fg>%KXw6v(p~$U5r`OS5~qD=tG# z036N)^$o*V9mz@&nPXZvOewj>@jbW-XDF4oN3lN;+)lp_XfSkqTpXg5Pz&3oeft&( z$0N6k*#6Po94H;2*Oe_n<@%>loMJ9l0@FE@W7-M2>y*VH!rTHhkJqoy zA(v9mJx)Mv0I6$cZZ5U>5%wdpYT85C2a!VHOedRjYiA4hb({6~qd(N%yVKy9L`ENL zr@ZtaK57oV3kc+|QIswi~7c(^2sa$F;f z2qG2J*QToVH-GO2;p}E$TY^|(7u@I(?gY#f*+?(u5;oJ4de3KyV;yo(h)EQLvRfh| zRx~N-zb&2wKKlC1BFuVhp(;vJPo*-uQ^%ErD{2P}J3jC6@ce%(%PjxjvP|4P;gh~y zjR#wl38MSF5Ay9uE=u2tAoNvH08>S*O!=NAuX44v z&;;f-5geGD@E+d1$K-?7vcLrPDsA)@Hq;+=f0Q@Hpk1GfBzFF|&F3YMl*FR-U9B5q2?eiQeBC?d7YUbm>jP-N%*5w5c-7&!rg{x?O#dP`ea?)2>kq zrg@cB$XDSUJl%G}hsnj(#QUYM-d$wy?F1P2>*7!Ld&=O%-L&wS9F&${w`*B@*?ULMjEO~U zS5Bi>tJ8#2M5P@(2QO8si8i{tw3KG0`m!oq_(PtvW z967mfRc^-xkT10;o9en@H_0(pkIsxUz9s_Q<-vrw0?Of3Lxt$-LTP6DRq|$KhKUqv zpAy#}yBnS5-$*FeXNMM#0B2Fx(H@6-lDOK#M=>?&?=3C&TTkPZeP$AI)Hc5iATJv( z)BMboV$YNfZ|EIZi)^xe`c&T36k6GN!T$sxK4REYfMFQt#zcI9TFB<(=g)z#C%HCw z{tsz|kQvkR9q0oH`U^-HNZh%vuTO}ZoqX1w8qCj-CPs|U`Xx5>Y;1wB%srh5AfS|j z=nGgLLnwG(BAumh{mkVlD69p9Dm#Mya@gjz8QBk^^(G*XA^*MO70s@hcZIY_W{#Hj z__kR~Qr(~nsftSE0nF>+9Q*z+7r%8&cNG+p7MR`^Id?T≪{_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 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 literal 0 HcmV?d00001 diff --git a/docs/img/storage.diagram b/docs/img/storage.diagram new file mode 100644 index 0000000..086fa84 --- /dev/null +++ b/docs/img/storage.diagram @@ -0,0 +1,35 @@ ++------------------------+ +| "disk_interface" | +| | +-----------------+ +| "customization point" |<--->| "counters" | +| "for disk I/O for" | | "to keep stats" | +| "whole session" | +-----------------+ +| |<-- "async_read()" +| |<-- "async_write()" +| |<-- "async_hash()" +| |<-- "async_hash2()" +| |<-- "async_move_storage()" +| |<-- "async_check_files()" +| |<-- "async_stop_torrent()" +| |<-- "remove_torrent()" +| |<-- "async_rename_file()" +| |<-- "async_set_file_priority()" +| |<-- "async_delete_files()" +| |<-- "async_clear_piece()" ++------------------------+ + ^ + | + | "new_torrent()" + | "save-path, file_storage," + | "info_hash, allocation_mode" + | "file-priorities" + | "returns storage_holder" + | + v ++----------------------------+ +| "storage_holder" |+ +| "opaque handle to storage" || +-------------------+ +| "for a torrent" |<---+ "file_storage" | ++----------------------------+| | "standard piece" | + +----------------------------+ | "to file mapping" | + +-------------------+ diff --git a/docs/img/troubleshooting.dot b/docs/img/troubleshooting.dot new file mode 100644 index 0000000..c26931e --- /dev/null +++ b/docs/img/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/img/utp_stack.diagram b/docs/img/utp_stack.diagram new file mode 100644 index 0000000..b3b8085 --- /dev/null +++ b/docs/img/utp_stack.diagram @@ -0,0 +1,9 @@ ++------------------------+ +| "BitTorrent protocol" | ++------------------------+ +| "SSL" | ++------------+-----------+ +| "TCP" | "uTP" | +| +-----------+ +| | "UDP" | ++------------+-----------+ diff --git a/docs/img/write_disk_buffers.diagram b/docs/img/write_disk_buffers.diagram new file mode 100644 index 0000000..349cb8e --- /dev/null +++ b/docs/img/write_disk_buffers.diagram @@ -0,0 +1,15 @@ + + "decrypt in place" "move buffer ref." ++------------------+ "(no copy)" +--------------+ "(no copy)" +----------------+ +| "receive buffer" +--=------------>| "plain text" +--=------------->| "store buffer" | ++------------------+ | "buffer" | +------+---------+ + ^ +--------------+ | + | "read() on socket" "write() to file" | + | "(copy)" "(copy)" | +---=----|---------------------------------=---------------------------------|--=---- + | "kernel space" memcpy() into | + | mmap v ++-------+---------+ +---------------------+ +| "socket kernel" | | "kernel page cache" | +| "buffer" | | | ++-----------------+ +---------------------+ diff --git a/docs/include/libtorrent/add_torrent_params.hpp b/docs/include/libtorrent/add_torrent_params.hpp new file mode 100644 index 0000000..11befb5 --- /dev/null +++ b/docs/include/libtorrent/add_torrent_params.hpp @@ -0,0 +1,401 @@ +/* + +Copyright (c) 2009-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2019, ghbplayer +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/info_hash.hpp" +#include "libtorrent/download_priority.hpp" +#include "libtorrent/client_data.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/fwd.hpp" + +namespace libtorrent { + +TORRENT_VERSION_NAMESPACE_3 + + // 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 + { + // hidden + add_torrent_params(); + ~add_torrent_params(); + add_torrent_params(add_torrent_params&&) noexcept; + add_torrent_params& operator=(add_torrent_params&&) &; + 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) \ + TORRENT_DEPRECATED static constexpr torrent_flags_t 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; + + // The ``userdata`` parameter is optional and will be passed on to the + // extension constructor functions, if any + // (see torrent_handle::add_extension()). It will also be stored in the + // torrent object and can be retrieved by calling userdata(). + client_data_t userdata; + + // 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&, client_data_t)>>> + 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; + +#if TORRENT_ABI_VERSION < 3 + // backwards compatible v1 hash, or truncated v2 + TORRENT_DEPRECATED + sha1_hash info_hash; +#endif + + // 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. + info_hash_t info_hashes; + + // ``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 TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // 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. + TORRENT_DEPRECATED aux::noexcept_movable> merkle_tree; +#endif + + // v2 hashes, if known + aux::vector, file_index_t> merkle_trees; + + // if set, indicates which hashes are included in the corresponding + // vector of ``merkle_trees``. These bitmasks always cover the full + // tree, a cleared bit means the hash is all zeros (i.e. not set) and + // set bit means the next hash in the corresponding vector in + // ``merkle_trees`` is the hash for that node. This is an optimization + // to avoid storing a lot of zeros. + aux::vector, file_index_t> merkle_tree_mask; + + // bit-fields indicating which v2 leaf hashes have been verified + // against the root hash. If this vector is empty and merkle_trees is + // non-empty it implies that all hashes in merkle_trees are verified. + aux::vector, file_index_t> verified_leaf_hashes; + + // 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. + TORRENT_DEPRECATED std::string url; + + // 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()``. + TORRENT_DEPRECATED aux::noexcept_movable> 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_3_END + +namespace aux { + bool contains_resume_data(add_torrent_params const&); +} + +} + +#endif diff --git a/docs/include/libtorrent/address.hpp b/docs/include/libtorrent/address.hpp new file mode 100644 index 0000000..0639c7b --- /dev/null +++ b/docs/include/libtorrent/address.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2006, 2009, 2015, 2017-2020, Arvid Norberg +Copyright (c) 2016, 2018, 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_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 +#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 + + using boost::asio::ip::network_v4; + using boost::asio::ip::make_network_v4; + using boost::asio::ip::v4_mapped; + +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::ip::make_address; + using sim::asio::ip::make_address_v4; + using sim::asio::ip::make_address_v6; +#else + using boost::asio::ip::make_address; + using boost::asio::ip::make_address_v4; + using boost::asio::ip::make_address_v6; +#endif + +} + +#endif diff --git a/docs/include/libtorrent/alert.hpp b/docs/include/libtorrent/alert.hpp new file mode 100644 index 0000000..35c8544 --- /dev/null +++ b/docs/include/libtorrent/alert.hpp @@ -0,0 +1,333 @@ +/* + +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2004-2005, 2008-2009, 2013-2020, Arvid Norberg +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_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 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.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()) + struct TORRENT_EXPORT alert + { +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // hidden + TORRENT_UNEXPORT alert(alert const& rhs) = delete; + alert& operator=(alert const&) = delete; + alert(alert&& rhs) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + 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 + TORRENT_DEPRECATED + static constexpr alert_category_t debug_notification = connect_notification; +#endif + static constexpr alert_category_t status_notification = 6_bit; +#if TORRENT_ABI_VERSION == 1 + // Alerts for when blocks are requested and completed. Also when + // pieces are completed. + TORRENT_DEPRECATED + static constexpr alert_category_t 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; + +#if TORRENT_ABI_VERSION <= 2 + // 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. + TORRENT_DEPRECATED + static constexpr alert_category_t stats_notification = 11_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 + TORRENT_UNEXPORT 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; + + 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/docs/include/libtorrent/alert_types.hpp b/docs/include/libtorrent/alert_types.hpp new file mode 100644 index 0000000..aa1efa8 --- /dev/null +++ b/docs/include/libtorrent/alert_types.hpp @@ -0,0 +1,2991 @@ +/* + +Copyright (c) 2017, toinetoine +Copyright (c) 2004-2020, Arvid Norberg +Copyright (c) 2008, Andrew Resch +Copyright (c) 2014-2018, Steven Siloti +Copyright (c) 2015, Thomas +Copyright (c) 2015-2018, Alden Torres +Copyright (c) 2017, Antoine Dahan +Copyright (c) 2018, d-komarov +Copyright (c) 2019, ghbplayer +Copyright (c) 2020, Fonic +Copyright (c) 2020, Viktor Elofsson +Copyright (c) 2020, AllSeeingEyeTolledEweSew +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/tracker_manager.hpp" // for event_t +#include "libtorrent/socket_type.hpp" +#include "libtorrent/client_data.hpp" +#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 = 99; + + // internal + constexpr int abi_alert_count = 128; + + // internal + enum class alert_priority : std::uint8_t + { + // the order matters here. Lower value means lower priority, and will + // start getting dropped earlier when the alert queue is filling up + normal = 0, + high, + critical, + meta + }; + + // 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_3 + + // This is a base class for alerts that are associated with a + // specific torrent. It contains a handle to the torrent. + // + // Note that by the time the client receives a torrent_alert, its + // ``handle`` member may be invalid. + struct TORRENT_EXPORT torrent_alert : alert + { + // internal + TORRENT_UNEXPORT torrent_alert(aux::stack_allocator& alloc, torrent_handle const& h); + TORRENT_UNEXPORT torrent_alert(torrent_alert&&) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static int const 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: + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT peer_alert(aux::stack_allocator& alloc, torrent_handle const& h, + tcp::endpoint const& i, peer_id const& pi); + TORRENT_UNEXPORT peer_alert(peer_alert&& rhs) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static int const 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. + TORRENT_DEPRECATED aux::noexcept_movable 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 + TORRENT_UNEXPORT tracker_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, string_view u); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static int const 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 + TORRENT_DEPRECATED std::string url; +#endif + }; + +#define TORRENT_DEFINE_ALERT_IMPL(name, seq, prio) \ + name(name&&) noexcept = default; \ + static alert_priority const priority = prio; \ + static int const 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 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // 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 ``alert_category::status`` 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_UNEXPORT 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; + }; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + + // The ``torrent_removed_alert`` is posted whenever a torrent is removed. Since + // the torrent handle in its base class will usually be invalid (since the torrent + // is already removed) it has the info hash as a member, to identify it. + // It's posted when the ``alert_category::status`` bit is set in the alert_mask. + // + // Note that the ``handle`` remains valid for some time after + // torrent_removed_alert is posted, as long as some internal libtorrent + // task (such as an I/O task) refers to it. Additionally, other alerts like + // save_resume_data_alert may be posted after torrent_removed_alert. + // To synchronize on whether the torrent has been removed or not, call + // torrent_handle::in_session(). This will return true before + // torrent_removed_alert is posted, and false afterward. + // + // 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_UNEXPORT torrent_removed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, info_hash_t const& ih, client_data_t userdata); + + 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; +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + info_hash_t info_hashes; + + // '`userdata`` as set in add_torrent_params at torrent creation. + // This can be used to associate this torrent with related data + // in the client application more efficiently than info_hashes. + client_data_t userdata; + }; + + // 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 + TORRENT_UNEXPORT read_piece_alert(aux::stack_allocator& alloc, torrent_handle const& h + , piece_index_t p, boost::shared_array d, int s); + TORRENT_UNEXPORT 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 + TORRENT_DEPRECATED error_code 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 + TORRENT_UNEXPORT 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) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + static constexpr alert_category_t static_category = + alert_category::file_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + 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 + TORRENT_UNEXPORT file_renamed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , string_view n, string_view old, 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; + + // returns the new and previous file name, respectively. + char const* new_name() const; + char const* old_name() const; + + // refers to the index of the file that was renamed, + file_index_t const index; + private: + aux::allocation_slot m_name_idx; + aux::allocation_slot m_old_name_idx; +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_warnings_push.hpp" + + public: + TORRENT_DEPRECATED std::string name; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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. + struct TORRENT_EXPORT tracker_error_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT tracker_error_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , int times, string_view u, operation_t op, 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; + + // This member says how many times in a row this tracker has failed. + int const times_in_row; + + // the error code indicating why the tracker announce failed. If it is + // is ``lt::errors::tracker_failure`` the failure_reason() might contain + // a more detailed description of why the tracker rejected the request. + // HTTP status codes indicating errors are also set in this field. + error_code const error; + + operation_t op; + + // if the tracker sent a "failure reason" string, it will be returned + // here. + char const* failure_reason() const; + + // hidden + char const* error_message() const { return failure_reason(); } + + private: + aux::allocation_slot m_msg_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED int const status_code; + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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. + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is generated when a scrape request succeeds. + struct TORRENT_EXPORT scrape_reply_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT scrape_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, error_code const& e); + TORRENT_UNEXPORT 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. + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT tracker_announce_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, event_t 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. See event_t. + event_t 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_DEPRECATED int const operation; + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is posted every time an incoming peer connection both + // successfully passes the protocol handshake and is associated with a + // torrent, or an outgoing peer connection attempt succeeds. For arbitrary + // incoming connections, see incoming_connection_alert. + struct TORRENT_EXPORT peer_connect_alert final : peer_alert + { + enum class direction_t { in, out }; + + // internal + TORRENT_UNEXPORT peer_connect_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, socket_type_t type, direction_t direction); + + TORRENT_DEFINE_ALERT(peer_connect_alert, 23) + + static constexpr alert_category_t static_category = alert_category::connect; + std::string message() const override; + + // Tells you if the peer was incoming or outgoing + direction_t direction; + + socket_type_t 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 + TORRENT_UNEXPORT peer_disconnected_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , peer_id const& peer_id, operation_t op, socket_type_t 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 + socket_type_t 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 + TORRENT_DEPRECATED int const operation; + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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_UNEXPORT 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 + TORRENT_UNEXPORT piece_finished_alert(aux::stack_allocator& alloc, + torrent_handle const& h, piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(piece_finished_alert, 27) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + static constexpr alert_category_t static_category = + alert_category::piece_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + 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 + TORRENT_UNEXPORT 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) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + | alert_category::peer + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + 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 + TORRENT_UNEXPORT 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) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + | alert_category::peer + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + 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 + TORRENT_UNEXPORT 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) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + 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 + TORRENT_UNEXPORT 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) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED char const* 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT storage_moved_alert(aux::stack_allocator& alloc + , torrent_handle const& h, string_view p, string_view old); + + 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 and from, respectively. + char const* storage_path() const; + char const* old_path() const; + + private: + aux::allocation_slot m_path_idx; + aux::allocation_slot m_old_path_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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: + TORRENT_DEPRECATED char const* operation; + // If the error happened for a specific file, ``file`` is its path. + TORRENT_DEPRECATED std::string file; +#endif + }; + + // This alert is generated when a request to delete the files of a torrent complete. + // + // This alert is posted in the ``alert_category::storage`` category, and that bit + // needs to be set in the alert_mask. + struct TORRENT_EXPORT torrent_deleted_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_deleted_alert(aux::stack_allocator& alloc + , torrent_handle const& h, info_hash_t 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; + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + + // 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_hashes`` member + // is hence the main way of identifying which torrent just completed the delete. + info_hash_t info_hashes; + }; + + // 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_UNEXPORT torrent_delete_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e, info_hash_t 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; + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + // the info hash of the torrent whose files failed to be deleted + info_hash_t info_hashes; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT save_resume_data_alert(aux::stack_allocator& alloc + , add_torrent_params&& params + , torrent_handle const& h); + TORRENT_UNEXPORT 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. + TORRENT_DEPRECATED std::shared_ptr 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 + TORRENT_UNEXPORT 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 + TORRENT_DEPRECATED std::string 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_UNEXPORT 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_UNEXPORT 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_UNEXPORT 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 + TORRENT_UNEXPORT url_seed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , string_view u, error_code const& e); + TORRENT_UNEXPORT 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 + TORRENT_DEPRECATED std::string url; + + // the error message, potentially from the server + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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: + TORRENT_DEPRECATED char const* operation; + // the path to the file that was accessed when the error occurred. + TORRENT_DEPRECATED std::string file; + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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(); + // 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_hashes().get_best().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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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; + }; + + // 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 + TORRENT_UNEXPORT 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); + TORRENT_UNEXPORT 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); + TORRENT_UNEXPORT 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); + TORRENT_UNEXPORT 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. + TORRENT_DEPRECATED int const operation; + + // the address and port libtorrent attempted to listen on + TORRENT_DEPRECATED aux::noexcept_movable endpoint; + + // the type of listen socket this alert refers to. + TORRENT_DEPRECATED socket_type_t 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 + TORRENT_UNEXPORT listen_succeeded_alert(aux::stack_allocator& alloc + , lt::address const& listen_addr + , int listen_port + , lt::socket_type_t t); + TORRENT_UNEXPORT listen_succeeded_alert(aux::stack_allocator& alloc + , tcp::endpoint const& ep + , lt::socket_type_t t); + TORRENT_UNEXPORT 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. + TORRENT_DEPRECATED aux::noexcept_movable endpoint; + + // the type of listen socket this alert refers to. + TORRENT_DEPRECATED socket_type_t 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 + TORRENT_UNEXPORT portmap_error_alert(aux::stack_allocator& alloc, port_mapping_t i + , portmap_transport t, error_code const& e, address const& local); + + 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; + + // the local network the port mapper is running on + aux::noexcept_movable
    local_address; + + // tells you what failed. + error_code const error; +#if TORRENT_ABI_VERSION == 1 + // is 0 for NAT-PMP and 1 for UPnP. + TORRENT_DEPRECATED int const map_type; + + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT portmap_alert(aux::stack_allocator& alloc, port_mapping_t i, int port + , portmap_transport t, portmap_protocol protocol, address const& local); + + 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; + + // the local network the port mapper is running on + aux::noexcept_movable
    local_address; + +#if TORRENT_ABI_VERSION == 1 + enum TORRENT_DEPRECATED_ENUM protocol_t + { + tcp, + udp + }; + + // the protocol this mapping was for. one of protocol_t enums + TORRENT_DEPRECATED int const protocol; + + // 0 for NAT-PMP and 1 for UPnP. + TORRENT_DEPRECATED int const 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 + TORRENT_UNEXPORT portmap_log_alert(aux::stack_allocator& alloc, portmap_transport t + , const char* m, address const& local); + + 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 local network the port mapper is running on + aux::noexcept_movable
    local_address; + + // 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: + TORRENT_DEPRECATED int const map_type; + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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. + TORRENT_DEPRECATED char const* operation; + + // If the error happened to a specific file, ``file`` is the path to it. + TORRENT_DEPRECATED std::string file; + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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, + ssrf_mitigation + }; + + // 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 ``alert_category::dht`` category. + struct TORRENT_EXPORT dht_announce_alert final : alert + { + // internal + TORRENT_UNEXPORT 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 ``alert_category::dht`` category. + struct TORRENT_EXPORT dht_get_peers_alert final : alert + { + // internal + TORRENT_UNEXPORT 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; + }; + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/aux_/disable_deprecation_warnings_push.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 TORRENT_DEPRECATED_EXPORT stats_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT 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; + }; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif // TORRENT_ABI_VERSION + + // 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 ``alert_category::storage`` 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 + TORRENT_UNEXPORT 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 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // 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 + TORRENT_UNEXPORT 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; + }; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_DEPRECATED std::string trackerid; +#endif + }; + + // This alert is posted when the initial DHT bootstrap is done. + struct TORRENT_EXPORT dht_bootstrap_alert final : alert + { + // internal + explicit TORRENT_UNEXPORT 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_UNEXPORT 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. + TORRENT_DEPRECATED std::string 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_UNEXPORT 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 + TORRENT_DEPRECATED error_code const 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 + TORRENT_UNEXPORT incoming_connection_alert(aux::stack_allocator& alloc + , socket_type_t 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 + socket_type_t 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. + TORRENT_DEPRECATED aux::noexcept_movable 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 + TORRENT_UNEXPORT add_torrent_alert(aux::stack_allocator& alloc, torrent_handle const& h + , add_torrent_params 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; + + // This contains copies of the most important fields from the original + // add_torrent_params object, passed to add_torrent() or + // async_add_torrent(). Specifically, these fields are copied: + // + // * version + // * ti + // * name + // * save_path + // * userdata + // * tracker_id + // * flags + // * info_hash + // + // the info_hash field will be updated with the info-hash of the torrent + // specified by ``ti``. + 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 ``alert_category::status``, but it's not subject to + // filtering, since it's only manually posted anyway. + struct TORRENT_EXPORT state_update_alert final : alert + { + // internal + TORRENT_UNEXPORT 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 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + 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; + }; +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#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. This alert does not + // have a category, since it's only posted in response to an API call. It + // is not subject to the alert_mask filter. + // + // 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 + TORRENT_UNEXPORT session_stats_alert(aux::stack_allocator& alloc, counters const& cnt); + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" +#endif + + TORRENT_DEFINE_ALERT_PRIO(session_stats_alert, 70, alert_priority::critical) + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + + static constexpr alert_category_t static_category = {}; + 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 + TORRENT_DEPRECATED std::array const values; +#else + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_counters_idx; +#endif + }; + + // 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 + TORRENT_UNEXPORT 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 + TORRENT_DEPRECATED op_t const 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 + TORRENT_UNEXPORT dht_immutable_item_alert(aux::stack_allocator& alloc, sha1_hash const& t + , entry 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 + TORRENT_UNEXPORT dht_mutable_item_alert(aux::stack_allocator& alloc + , std::array const& k, std::array const& sig + , std::int64_t sequence, string_view s, entry 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 + TORRENT_UNEXPORT dht_put_alert(aux::stack_allocator& alloc, sha1_hash const& t, int n); + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 ``alert_category::dht`` category. + struct TORRENT_EXPORT dht_outgoing_get_peers_alert final : alert + { + // internal + TORRENT_UNEXPORT 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 + TORRENT_DEPRECATED aux::noexcept_movable 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 + TORRENT_UNEXPORT log_alert(aux::stack_allocator& alloc, char const* log); + TORRENT_UNEXPORT 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_UNEXPORT 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 + TORRENT_UNEXPORT 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 ``alert_category::error``. + struct TORRENT_EXPORT lsd_error_alert final : alert + { + // internal + TORRENT_UNEXPORT lsd_error_alert(aux::stack_allocator& alloc, error_code const& ec + , address const& local); + + TORRENT_DEFINE_ALERT(lsd_error_alert, 82) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // the local network the corresponding local service discovery is running + // on + aux::noexcept_movable
    local_address; + + // 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 + TORRENT_UNEXPORT dht_stats_alert(aux::stack_allocator& alloc + , std::vector table + , std::vector requests + , sha1_hash id, udp::endpoint ep); + + TORRENT_DEFINE_ALERT(dht_stats_alert, 83) + + static constexpr alert_category_t static_category = {}; + 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; + + // the node ID of the DHT node instance + sha1_hash nid; + + // the local socket this DHT node is running on + aux::noexcept_movable local_endpoint; + }; + + // 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 + TORRENT_UNEXPORT 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 alert_category::dht_log 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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: + TORRENT_DEPRECATED direction_t 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 + TORRENT_UNEXPORT dht_get_peers_reply_alert(aux::stack_allocator& alloc + , sha1_hash const& ih + , std::vector const& peers); + + 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 + TORRENT_UNEXPORT dht_direct_response_alert(aux::stack_allocator& alloc, client_data_t userdata + , udp::endpoint const& addr, bdecode_node const& response); + + // internal + // for when there was a timeout so we don't have a response + TORRENT_UNEXPORT dht_direct_response_alert(aux::stack_allocator& alloc, client_data_t 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; + + client_data_t 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: + TORRENT_DEPRECATED aux::noexcept_movable 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 + // alert_category::picker_log). + struct TORRENT_EXPORT picker_log_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 TORRENT_UNEXPORT session_stats_header_alert(aux::stack_allocator& alloc); + TORRENT_DEFINE_ALERT(session_stats_header_alert, 92) + + static constexpr alert_category_t static_category = {}; + 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 + TORRENT_UNEXPORT dht_sample_infohashes_alert(aux::stack_allocator& alloc + , sha1_hash const& nid + , 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; + + // id of the node the request was sent to (and this response was received from) + sha1_hash node_id; + + // 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 ``alert_category::upload`` category. + struct TORRENT_EXPORT block_uploaded_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT 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) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::upload + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + 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 TORRENT_UNEXPORT alerts_dropped_alert(aux::stack_allocator& alloc + , std::bitset const&); + TORRENT_DEFINE_ALERT_PRIO(alerts_dropped_alert, 95, alert_priority::meta) + + 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; + static_assert(num_alert_types <= abi_alert_count, "need to increase bitset. This is an ABI break"); + }; + + // this alert is posted with SOCKS5 related errors, when a SOCKS5 proxy is + // configured. It's enabled with the alert_category::error 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; + }; + + // posted when a prioritize_files() or file_priority() update of the file + // priorities complete, which requires a round-trip to the disk thread. + // + // If the disk operation fails this alert won't be posted, but a + // file_error_alert is posted instead, and the torrent is stopped. + struct TORRENT_EXPORT file_prio_alert final : torrent_alert + { + // internal + explicit file_prio_alert(aux::stack_allocator& alloc, torrent_handle h); + TORRENT_DEFINE_ALERT(file_prio_alert, 97) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // the error + error_code error; + + // the operation that failed + operation_t op; + }; + +TORRENT_VERSION_NAMESPACE_3_END + + // this alert may be posted when the initial checking of resume data and files + // on disk (just existence, not piece hashes) completes. If a file belonging + // to the torrent is found on disk, but is larger than the file in the + // torrent, that's when this alert is posted. + // the client may want to call truncate_files() in that case, or perhaps + // interpret it as a sign that some other file is in the way, that shouldn't + // be overwritten. + struct TORRENT_EXPORT oversized_file_alert final : torrent_alert + { + // internal + explicit oversized_file_alert(aux::stack_allocator& alloc, torrent_handle h); + TORRENT_DEFINE_ALERT(oversized_file_alert, 98) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // hidden + file_index_t reserved; + }; + + // 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 + +} // namespace libtorrent + +#endif diff --git a/docs/include/libtorrent/announce_entry.hpp b/docs/include/libtorrent/announce_entry.hpp new file mode 100644 index 0000000..1f3ce25 --- /dev/null +++ b/docs/include/libtorrent/announce_entry.hpp @@ -0,0 +1,291 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2016, 2018, 2020, Alden Torres +Copyright (c) 2017-2018, 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_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_/array.hpp" +#include "libtorrent/info_hash.hpp" + +#include +#include +#include + +namespace libtorrent { + + struct torrent; + +TORRENT_VERSION_NAMESPACE_2 + + struct TORRENT_EXPORT announce_infohash + { + // internal + TORRENT_UNEXPORT announce_infohash(); + + // 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 time of next tracker announce + time_point32 next_announce = (time_point32::min)(); + + // no announces before this time + time_point32 min_announce = (time_point32::min)(); + + // 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; + +#if TORRENT_ABI_VERSION <= 2 + // reset announce counters and clears the started sent flag. + // The announce_endpoint will look like we've never talked to + // the tracker. + TORRENT_DEPRECATED void reset(); + + // updates the failure counter and time-outs for re-trying. + // This is called when the tracker announce fails. + TORRENT_DEPRECATED 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. + TORRENT_DEPRECATED 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. + TORRENT_DEPRECATED bool is_working() const { return fails == 0; } +#endif + }; + + // announces are sent to each tracker using every listen socket + // this class holds information about one listen socket for one tracker +#if TORRENT_ABI_VERSION <= 2 + // this is to suppress deprecation warnings from implicit move constructor +#include "libtorrent/aux_/disable_warnings_push.hpp" +#endif + struct TORRENT_EXPORT announce_endpoint + { +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + + announce_endpoint(); + + // the local endpoint of the listen interface associated with this endpoint + tcp::endpoint local_endpoint; + + // torrents can be announced using multiple info hashes + // for different protocol versions + + // info_hashes[0] is the v1 info hash (SHA1) + // info_hashes[1] is the v2 info hash (truncated SHA-256) + aux::array info_hashes; + +#if TORRENT_ABI_VERSION <= 2 + // reset announce counters and clears the started sent flag. + // The announce_endpoint will look like we've never talked to + // the tracker. + TORRENT_DEPRECATED void reset(); + + // deprecated in 2.0, use announce_infohash::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, std::uint8_t fail_limit) const; + + // deprecated in 2.0, use announce_infohash::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; + + // for backwards compatibility + TORRENT_DEPRECATED time_point32 next_announce = (time_point32::min)(); + TORRENT_DEPRECATED time_point32 min_announce = (time_point32::min)(); + TORRENT_DEPRECATED std::string message; + TORRENT_DEPRECATED error_code last_error; + TORRENT_DEPRECATED int scrape_incomplete = -1; + TORRENT_DEPRECATED int scrape_complete = -1; + TORRENT_DEPRECATED int scrape_downloaded = -1; + TORRENT_DEPRECATED std::uint8_t fails : 7; + TORRENT_DEPRECATED bool updating : 1; + TORRENT_DEPRECATED bool start_sent : 1; + TORRENT_DEPRECATED bool complete_sent : 1; +#endif + + // set to false to not announce from this endpoint + bool enabled = true; + }; + + // 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 + TORRENT_DEPRECATED std::uint8_t fails:7; + TORRENT_DEPRECATED bool send_stats:1; + TORRENT_DEPRECATED bool start_sent:1; + TORRENT_DEPRECATED bool complete_sent:1; + // internal + TORRENT_DEPRECATED bool triggered_manually:1; + TORRENT_DEPRECATED bool updating:1; +#endif + +#if TORRENT_ABI_VERSION <= 2 + // reset announce counters and clears the started sent flag. + // The announce_entry will look like we've never talked to + // the tracker. + TORRENT_DEPRECATED void reset(); + + // trims whitespace characters from the beginning of the URL. + TORRENT_DEPRECATED void trim(); +#endif + +#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 + }; + +TORRENT_VERSION_NAMESPACE_2_END + +} + +#endif diff --git a/docs/include/libtorrent/assert.hpp b/docs/include/libtorrent/assert.hpp new file mode 100644 index 0000000..2bf6ee8 --- /dev/null +++ b/docs/include/libtorrent/assert.hpp @@ -0,0 +1,130 @@ +/* + +Copyright (c) 2007-2008, 2010-2011, 2013-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_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/docs/include/libtorrent/bdecode.hpp b/docs/include/libtorrent/bdecode.hpp new file mode 100644 index 0000000..7bde991 --- /dev/null +++ b/docs/include/libtorrent/bdecode.hpp @@ -0,0 +1,466 @@ +/* + +Copyright (c) 2015-2016, Alden Torres +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 aux { + +// 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_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. + // the ``data_offset()`` function returns the byte-offset to this node in, + // starting from the beginning of the buffer that was parsed. + span data_section() const noexcept; + std::ptrdiff_t data_offset() 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 ``string_view`` 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). + // + // ``dict_at()`` returns the (key, value)-pair at the specified index in a + // dictionary. Keys are only allowed to be strings. ``dict_at_node()`` also + // returns the (key, value)-pair, but the key is returned as a + // ``bdecode_node`` (and it will always be a string). + bdecode_node dict_find(string_view key) const; + std::pair dict_at(int i) const; + std::pair dict_at_node(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_offset()`` returns the byte offset from the start of the parsed + // bencoded buffer this string can be found. + string_view string_value() const; + char const* string_ptr() const; + int string_length() const; + std::ptrdiff_t string_offset() 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(aux::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 + aux::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/docs/include/libtorrent/bencode.hpp b/docs/include/libtorrent/bencode.hpp new file mode 100644 index 0000000..dd78725 --- /dev/null +++ b/docs/include/libtorrent/bencode.hpp @@ -0,0 +1,408 @@ +/* + +Copyright (c) 2003-2005, 2007-2009, 2012-2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2019, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 aux { + + 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"); + std::array buf; + 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 aux::bencode_recursive(out, e); + } + +#if TORRENT_ABI_VERSION == 1 + template + TORRENT_DEPRECATED + entry bdecode(InIt start, InIt end) + { + entry e; + bool err = false; + aux::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; + aux::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/docs/include/libtorrent/bitfield.hpp b/docs/include/libtorrent/bitfield.hpp new file mode 100644 index 0000000..fd08842 --- /dev/null +++ b/docs/include/libtorrent/bitfield.hpp @@ -0,0 +1,326 @@ +/* + +Copyright (c) 2008-2009, 2012-2019, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Falcosc +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/docs/include/libtorrent/bloom_filter.hpp b/docs/include/libtorrent/bloom_filter.hpp new file mode 100644 index 0000000..ce062a7 --- /dev/null +++ b/docs/include/libtorrent/bloom_filter.hpp @@ -0,0 +1,80 @@ +/* + +Copyright (c) 2010-2011, 2015-2017, 2019, Arvid Norberg +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_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* k, std::uint8_t* bits, int len); + TORRENT_EXTRA_EXPORT bool has_bits(std::uint8_t const* k, 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 + { + int const c = (std::min)(count_zero_bits(bits, N), (N * 8) - 1); + int const 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/docs/include/libtorrent/bt_peer_connection.hpp b/docs/include/libtorrent/bt_peer_connection.hpp new file mode 100644 index 0000000..776ebed --- /dev/null +++ b/docs/include/libtorrent/bt_peer_connection.hpp @@ -0,0 +1,519 @@ +/* + +Copyright (c) 2007, Un Shyam +Copyright (c) 2006-2020, Arvid Norberg +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2018-2019, Steven Siloti +Copyright (c) 2018, Greg Hazel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/aux_/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" +#include "libtorrent/hash_picker.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 + + struct TORRENT_EXTRA_EXPORT bt_peer_connection + : peer_connection + { + friend struct invariant_access; + + // 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& 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, + + msg_hash_request = 21, + msg_hashes, + msg_hash_reject, + + 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); + void on_hash_request(int received); + void on_hashes(int received); + void on_hash_reject(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(); + + template + void extension_notify(F message, Args... args); + + // 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); + void write_hash_request(hash_request const& req); + void write_hashes(hash_request const& req, span hashes); + void write_hash_reject(hash_request const& req, sha256_hash const& file_root); + + void maybe_send_hash_request(); + + // 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, (aux::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 + aux::buffer buf(size, {holder.data(), size}); + append_send_buffer(std::move(buf), size); + } + else +#endif + { + append_send_buffer(std::move(holder), size); + } + } + + private: + +#if !defined TORRENT_DISABLE_ENCRYPTION + void init_bt_handshake(); +#endif + + 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, +#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 + aux::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; + + std::vector m_hash_requests; + +#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/docs/include/libtorrent/choker.hpp b/docs/include/libtorrent/choker.hpp new file mode 100644 index 0000000..e593ca0 --- /dev/null +++ b/docs/include/libtorrent/choker.hpp @@ -0,0 +1,60 @@ +/* + +Copyright (c) 2014, 2017-2020, Arvid Norberg +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_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; +} + struct 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 + , time_duration unchoke_interval + , aux::session_settings const& sett); + +} + +#endif // TORRENT_CHOKER_INCLUDED diff --git a/docs/include/libtorrent/client_data.hpp b/docs/include/libtorrent/client_data.hpp new file mode 100644 index 0000000..885fdd4 --- /dev/null +++ b/docs/include/libtorrent/client_data.hpp @@ -0,0 +1,104 @@ +/* + +Copyright (c) 2019-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_CLIENT_DATA_HPP_INCLUDED +#define TORRENT_CLIENT_DATA_HPP_INCLUDED + +#include "libtorrent/aux_/export.hpp" +#include + +namespace libtorrent { + +// A thin wrapper around a void pointer used as "user data". i.e. an opaque +// cookie passed in to libtorrent and returned on demand. It adds type-safety by +// requiring the same type be requested out of it as was assigned to it. +struct TORRENT_EXPORT client_data_t +{ + // construct a nullptr client data + client_data_t() = default; + + // initialize the client data with the specified pointer + template + explicit client_data_t(T* v) + : m_type_ptr(type()) + , m_client_ptr(v) + {} + + // assigns a new pointer to the client data + template + client_data_t& operator=(T* v) + { + m_type_ptr = type(); + m_client_ptr = v; + return *this; + } + + // request to retrieve the pointer back again. The type ``T`` must be + // identical to the type of the pointer assigned earlier, including + // cv-qualifiers. + template + T* get() const + { + if (m_type_ptr != type()) return nullptr; + return static_cast(m_client_ptr); + } + template ::value>::type> + explicit operator T() const + { + if (m_type_ptr != type::type>()) return nullptr; + return static_cast(m_client_ptr); + } + +#if TORRENT_ABI_VERSION > 2 + // we don't allow type-unsafe operations + operator void*() const = delete; + operator void const*() const = delete; + client_data_t& operator=(void*) = delete; + client_data_t& operator=(void const*) = delete; +#endif + +private: + template + char const* type() const + { + // each unique T will instantiate a unique "instance", and have a unique + // address + static const char instance = 0; + return &instance; + } + char const* m_type_ptr = nullptr; + void* m_client_ptr = nullptr; +}; + +} + +#endif diff --git a/docs/include/libtorrent/close_reason.hpp b/docs/include/libtorrent/close_reason.hpp new file mode 100644 index 0000000..8a22b29 --- /dev/null +++ b/docs/include/libtorrent/close_reason.hpp @@ -0,0 +1,156 @@ +/* + +Copyright (c) 2015, 2017, 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_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/docs/include/libtorrent/config.hpp b/docs/include/libtorrent/config.hpp new file mode 100644 index 0000000..6335dfc --- /dev/null +++ b/docs/include/libtorrent/config.hpp @@ -0,0 +1,656 @@ +/* + +Copyright (c) 2005, 2008-2020, Arvid Norberg +Copyright (c) 2015, John Sebastian Peterson +Copyright (c) 2016, terry zhao +Copyright (c) 2016-2017, 2019, Alden Torres +Copyright (c) 2016, 2019, Andrei Kurushin +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, Pavel Pimenov +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 "libtorrent/aux_/disable_warnings_push.hpp" + +#define _FILE_OFFSET_BITS 64 + +#include + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.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) + +#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 + +// ==== Darwin/BSD === +#elif (defined __APPLE__ && defined __MACH__) || defined __FreeBSD__ || defined __NetBSD__ \ + || defined __OpenBSD__ || defined __bsdi__ || defined __DragonFly__ \ + || defined __FreeBSD_kernel__ +#define TORRENT_BSD + +#if defined __APPLE__ + +#include +#include + +#if defined __MACH__ && MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +#define TORRENT_HAS_COPYFILE 1 +#endif + +#define TORRENT_NATIVE_UTF8 1 + +#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 +#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 + +#define TORRENT_USE_DEV_RANDOM 1 + +#else + +// non-Apple BSD +#define TORRENT_USE_GETRANDOM 1 +#define TORRENT_HAS_PTHREAD_SET_NAME 1 + +#endif // __APPLE__ + +#define TORRENT_HAS_SYMLINK 1 + +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 1 +#endif +#define TORRENT_USE_MADVISE 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 + +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 1 +#endif + +#if defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 27)) +#define TORRENT_HAS_COPY_FILE_RANGE 1 +#endif + +#define TORRENT_HAS_PTHREAD_SET_NAME 1 +#define TORRENT_HAS_SYMLINK 1 +#define TORRENT_USE_MADVISE 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 + +#if defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ > 24)) +#define TORRENT_USE_GETRANDOM 1 +#endif + +// ===== ANDROID ===== (almost linux, sort of) +#if defined __ANDROID__ +#define TORRENT_ANDROID +#define TORRENT_HAS_PTHREAD_SET_NAME 1 + +#if __ANDROID_API__ < 21 +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_HAS_FADVISE 0 +#endif // API < 21 + +// android 32 bits has real problems with fseeko +#if (__ANDROID_API__ < 24) || defined __arm__ || defined __i386__ +#define TORRENT_HAS_FSEEKO 0 +#endif + +#if __ANDROID_API__ < 24 +#define TORRENT_HAS_FTELLO 0 +#endif // API < 24 + +// Starting Android 11 (API >= 30), the enum_routes using NETLINK +// is not possible anymore. For other functions, it's not clear +// that IFADDRS is working as expected for API >= 30, but at least +// it is supported. +// See https://developer.android.com/training/articles/user-data-ids#mac-11-plus +#if __ANDROID_API__ >= 24 +#undef TORRENT_USE_NETLINK +#undef TORRENT_USE_IFADDRS +#define TORRENT_USE_NETLINK 0 +#define TORRENT_USE_IFADDRS 1 +#endif // API >= 24 + +#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_HAVE_MAP_VIEW_OF_FILE +#define TORRENT_HAVE_MAP_VIEW_OF_FILE 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 + +// mingw doesn't implement random_device. +#define TORRENT_BROKEN_RANDOM_DEVICE 1 + +# if !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#ifdef NTDDI_VERSION +# if (NTDDI_VERSION >= NTDDI_VISTA) +# define TORRENT_USE_CNG 1 +# endif +#else // NTDDI_VERSION not defined so use simple _WIN32_WINNT check +# if _WIN32_WINNT >= 0x0600 +# define TORRENT_USE_CNG 1 +# endif +#endif + +# if !defined TORRENT_USE_CNG +// unless some other crypto library has been specified, default to the native +// windows CryptoAPI +#define TORRENT_USE_CRYPTOAPI 1 +#define TORRENT_USE_DEV_RANDOM 0 + +#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 // !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#endif +// ==== WINDOWS === +#elif defined _WIN32 +#define TORRENT_WINDOWS +#ifndef TORRENT_USE_GETIPFORWARDTABLE +# define TORRENT_USE_GETIPFORWARDTABLE 1 +#endif + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifndef TORRENT_HAVE_MAP_VIEW_OF_FILE +#define TORRENT_HAVE_MAP_VIEW_OF_FILE 1 +#endif + +# if !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#ifdef NTDDI_VERSION +# if (NTDDI_VERSION >= NTDDI_VISTA) +# define TORRENT_USE_CNG 1 +# endif +#else // NTDDI_VERSION not defined so use simple _WIN32_WINNT check +# if _WIN32_WINNT >= 0x0600 +# define TORRENT_USE_CNG 1 +# endif +#endif + +# if !defined TORRENT_USE_CNG +// unless some other crypto library has been specified, default to the native +// windows CryptoAPI +#define TORRENT_USE_CRYPTOAPI 1 +#define TORRENT_USE_DEV_RANDOM 0 + +#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 // !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#endif + +#define TORRENT_USE_GETADAPTERSADDRESSES 1 +#define TORRENT_HAS_SALEN 0 +#define TORRENT_USE_RLIMIT 0 +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_USE_UNC_PATHS 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 +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 1 +#endif +#define TORRENT_USE_MADVISE 1 +#define TORRENT_HAS_SYMLINK 1 +#define TORRENT_USE_GETRANDOM 1 + +// ==== BEOS === +#elif defined __BEOS__ || defined __HAIKU__ +#define TORRENT_BEOS +#include // B_PATH_NAME_LENGTH +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_NATIVE_UTF8 1 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_USE_GRTTABLE 1 + +// ==== GNU/Hurd === +#elif defined __GNU__ +#define TORRENT_HURD +#define TORRENT_USE_IFADDRS 1 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_HAS_SYMLINK 1 +#define TORRENT_USE_GETRANDOM 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 + +#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 + +#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 + +#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_HAS_FADVISE +#define TORRENT_HAS_FADVISE 1 +#endif + +#ifndef TORRENT_HAS_FSEEKO +#define TORRENT_HAS_FSEEKO 1 +#endif + +#ifndef TORRENT_HAS_FTELLO +#define TORRENT_HAS_FTELLO 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_CNG +#define TORRENT_USE_CNG 0 +#endif + +#ifndef TORRENT_USE_DEV_RANDOM +#define TORRENT_USE_DEV_RANDOM 1 +#endif + +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 0 +#endif + +#ifndef TORRENT_HAVE_MAP_VIEW_OF_FILE +#define TORRENT_HAVE_MAP_VIEW_OF_FILE 0 +#endif + +#ifndef TORRENT_USE_MADVISE +#define TORRENT_USE_MADVISE 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 + +#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_HAS_SYMLINK +#define TORRENT_HAS_SYMLINK 0 +#endif + +#ifndef TORRENT_USE_IFCONF +#define TORRENT_USE_IFCONF 0 +#endif + +#ifndef TORRENT_USE_GETRANDOM +#define TORRENT_USE_GETRANDOM 0 +#endif + +#ifndef TORRENT_NATIVE_UTF8 +#define TORRENT_NATIVE_UTF8 0 +#endif + +#ifndef TORRENT_HAS_PTHREAD_SET_NAME +#define TORRENT_HAS_PTHREAD_SET_NAME 0 +#endif + +#ifndef TORRENT_HAS_COPY_FILE_RANGE +#define TORRENT_HAS_COPY_FILE_RANGE 0 +#endif + +#ifndef TORRENT_HAS_COPYFILE +#define TORRENT_HAS_COPYFILE 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 + +#if defined TORRENT_USE_OPENSSL || defined TORRENT_USE_GNUTLS +#define TORRENT_USE_SSL 1 +#else +#define TORRENT_USE_SSL 0 +#endif + +#if defined TORRENT_SSL_PEERS && !TORRENT_USE_SSL +#error compiling with TORRENT_SSL_PEERS requires TORRENT_USE_OPENSSL or TORRENT_USE_GNUTLS +#endif + +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent {} + +// create alias +namespace lt = libtorrent; + +#endif // TORRENT_CONFIG_HPP_INCLUDED diff --git a/docs/include/libtorrent/copy_ptr.hpp b/docs/include/libtorrent/copy_ptr.hpp new file mode 100644 index 0000000..c2cec73 --- /dev/null +++ b/docs/include/libtorrent/copy_ptr.hpp @@ -0,0 +1,68 @@ +/* + +Copyright (c) 2010, 2016-2019, Arvid Norberg +Copyright (c) 2017, Matthew Fioravante +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/docs/include/libtorrent/crc32c.hpp b/docs/include/libtorrent/crc32c.hpp new file mode 100644 index 0000000..300fb8e --- /dev/null +++ b/docs/include/libtorrent/crc32c.hpp @@ -0,0 +1,46 @@ +/* + +Copyright (c) 2010, 2014, 2016-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_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); + TORRENT_EXTRA_EXPORT std::uint32_t crc32c(std::uint64_t const*, int); +} + +#endif diff --git a/docs/include/libtorrent/create_torrent.hpp b/docs/include/libtorrent/create_torrent.hpp new file mode 100644 index 0000000..dc65586 --- /dev/null +++ b/docs/include/libtorrent/create_torrent.hpp @@ -0,0 +1,532 @@ +/* + +Copyright (c) 2008-2020, Arvid Norberg +Copyright (c) 2016, Markus +Copyright (c) 2016-2017, 2019, Alden Torres +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_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/hasher.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/session_params.hpp" // for disk_io_constructor_type +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/path.hpp" // for combine_path etc. +#include "libtorrent/fwd.hpp" +#include "libtorrent/aux_/throw.hpp" + +#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 { + + // 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 + +#if TORRENT_ABI_VERSION <= 2 + // 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. + // incompatible with v2 metadata, ignored + TORRENT_DEPRECATED static constexpr create_flags_t optimize_alignment = 0_bit; +#endif +#if TORRENT_ABI_VERSION == 1 + // same as optimize_alignment, for backwards compatibility + TORRENT_DEPRECATED static constexpr create_flags_t optimize = 0_bit; +#endif + +#if TORRENT_ABI_VERSION <= 2 + // 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. + // support for BEP 30 merkle torrents has been removed + TORRENT_DEPRECATED static constexpr create_flags_t merkle = 1_bit; +#endif + + // 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. +#if TORRENT_ABI_VERSION <= 2 + // BEP 52 requires files to be piece aligned so all torrents are now compatible + // with BEP 38 + TORRENT_DEPRECATED static constexpr create_flags_t mutable_torrent_support = 4_bit; +#endif + + // Do not generate v1 metadata. The resulting torrent will only be usable by + // clients which support v2. This requires setting all v2 hashes, with + // set_hash2() before calling generate(). Setting v1 hashes (with + // set_hash()) is an error with this flag set. + static constexpr create_flags_t v2_only = 5_bit; + + // do not generate v2 metadata or enforce v2 alignment and padding rules + // this is mainly for tests, not recommended for production use. This + // requires setting all v1 hashes, with set_hash(), before calling + // generate(). Setting v2 hashes (with set_hash2()) is an error with + // this flag set. + static constexpr create_flags_t v1_only = 6_bit; + + // This flag only affects v1-only torrents, and is only relevant + // together with the v1_only_flag. This flag will force the + // same file order and padding as a v2 (or hybrid) torrent would have. + // It has the effect of ordering files and inserting pad files to align + // them with piece boundaries. + static constexpr create_flags_t canonical_files = 7_bit; + + // passing this flag to add_files() will ignore file attributes (such as + // executable or hidden) when adding the files to the file storage. + // Since not all filesystems and operating systems support all file + // attributes the resulting torrent may differ depending on where it's + // created. If it's important for torrents to be created consistently + // across systems, this flag should be set. + static constexpr create_flags_t no_attributes = 8_bit; + + // The ``piece_size`` is the size of each piece in bytes. It must be a + // power of 2 and a minimum of 16 kiB. If a piece size of 0 is + // specified, a piece_size will be set automatically. + // + // The file_storage (``fs``) parameter defines the files, sizes and + // their properties for the torrent to be created. Set this up first, + // before passing it to the create_torrent constructor. + // + // 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. + // + // .. warning:: + // The file_storage and torrent_info objects must stay alive for the + // entire duration of the create_torrent object. + // + // The ``flags`` arguments specifies options for the torrent creation. It can + // be any combination of the flags defined by create_flags_t. + explicit create_torrent(file_storage& fs, int piece_size = 0 + , create_flags_t flags = {}); + explicit create_torrent(torrent_info const& ti); + +#if TORRENT_ABI_VERSION <= 2 + TORRENT_DEPRECATED + explicit create_torrent(file_storage& fs, int piece_size + , int, create_flags_t flags = {}, int = -1) + : create_torrent(fs, piece_size, flags) {} +#endif + + // 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. + // + // Whether the resulting torrent object is v1, v2 or hybrid depends on + // whether any of the v1_only or v2_only flags were set on the + // constructor. If neither were set, the resulting torrent depends on + // which hashes were set. If both v1 and v2 hashes were set, a hybrid + // torrent is created. + // + // Any failure will cause this function to throw system_error, with an + // appropriate error message. These are the reasons this call may throw: + // + // * the file storage has 0 files + // * the total size of the file storage is 0 bytes (i.e. it only has + // empty files) + // * not all v1 hashes (set_hash()) and not all v2 hashes (set_hash2()) + // were set + // * for v2 torrents, you may not have a directory with the same name as + // a file. If that's encountered in the file storage, generate() + // fails. + 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); + + // sets the "creation time" field. Defaults to the system clock at the + // time of construction of the create_torrent object. The timestamp is + // specified in seconds, posix time. If the creation date is set to 0, + // the "creation date" field will be omitted from the generated torrent. + void set_creation_date(std::time_t timestamp); + + // 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(). + // A SHA-1 hash of all zeros is internally used to indicate a hash that + // has not been set. Setting such hash will not be considered set when + // calling generate(). + // This function will throw ``std::system_error`` if it is called on an + // object constructed with the v2_only flag. + void set_hash(piece_index_t index, sha1_hash const& h); + + // sets the bittorrent v2 hash for file `file` of the piece `piece`. + // `piece` is relative to the first piece of the file, starting at 0. The + // first piece in the file can be computed with + // file_storage::file_index_at_piece(). + // The hash, `h`, is the root of the merkle tree formed by the piece's + // 16 kiB blocks. Note that piece sizes must be powers-of-2, so all + // per-piece merkle trees are complete. + // A SHA-256 hash of all zeros is internally used to indicate a hash + // that has not been set. Setting such hash will not be considered set + // when calling generate(). + // This function will throw ``std::system_error`` if it is called on an + // object constructed with the v1_only flag. + void set_hash2(file_index_t file, piece_index_t::diff_type piece, sha256_hash const& h); + +#if TORRENT_ABI_VERSION < 3 + // 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. + // .. note:: + // + // with bittorrent v2, this feature is obsolete + TORRENT_DEPRECATED + void set_file_hash(file_index_t index, sha1_hash const& h); +#endif + + // 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 cert); + + // 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; } + + bool is_v2_only() const { return m_v2_only; } + bool is_v1_only() const { return m_v1_only; } + + // 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); } + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // 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. + TORRENT_DEPRECATED + std::vector merkle_tree() const { return std::vector(); } +#endif + + // 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`_. + void add_similar_torrent(sha1_hash ih); + void add_collection(string_view c); + + private: + + file_storage const& 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; + + // leave this here for now, to preserve ABI between building with + // deprecated functions and without + aux::vector m_filehashes; + + mutable aux::vector m_fileroots; + aux::vector, file_index_t> m_file_piece_hash; + + std::vector m_similar; + std::vector m_collections; + + // 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, 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; + + bool m_v2_only:1; + + // only generate v1 metadata and do not enforce v2 padding rules + bool m_v1_only:1; + }; + +namespace aux { + 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 taking a settings_pack may be used to configure the + // underlying disk access. Such as ``settings_pack::aio_threads``. + // + // 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); + TORRENT_EXPORT void set_piece_hashes(create_torrent& t, std::string const& p + , settings_interface const& settings + , std::function const& f, error_code& ec); + TORRENT_EXPORT void set_piece_hashes(create_torrent& t, std::string const& p + , settings_interface const& settings, disk_io_constructor_type disk_io + , 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, aux::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, aux::nop, ec); + if (ec) aux::throw_ex(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) aux::throw_ex(ec); + } + inline void set_piece_hashes(create_torrent& t, std::string const& p + , settings_interface const& settings + , std::function const& f) + { + error_code ec; + set_piece_hashes(t, p, settings, f, ec); + if (ec) aux::throw_ex(ec); + } +#endif + +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/docs/include/libtorrent/deadline_timer.hpp b/docs/include/libtorrent/deadline_timer.hpp new file mode 100644 index 0000000..723ed9c --- /dev/null +++ b/docs/include/libtorrent/deadline_timer.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2009, 2015, 2017-2019, Arvid Norberg +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_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/docs/include/libtorrent/debug.hpp b/docs/include/libtorrent/debug.hpp new file mode 100644 index 0000000..0211d98 --- /dev/null +++ b/docs/include/libtorrent/debug.hpp @@ -0,0 +1,288 @@ +/* + +Copyright (c) 2003, 2010, 2012-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2017, 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_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 +#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 + TORRENT_EXTRA_EXPORT extern std::map _async_ops; + TORRENT_EXTRA_EXPORT extern int _async_ops_nthreads; + TORRENT_EXTRA_EXPORT extern std::mutex _async_ops_mutex; + + // timestamp -> operation + struct wakeup_t + { + time_point timestamp; + std::uint64_t context_switches; + char const* operation; + }; + TORRENT_EXTRA_EXPORT 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 (auto const& op : _async_ops) + { + if (op.second.refs <= _async_ops_nthreads - 1) continue; + ret += op.second.refs; + std::printf("%s: (%d)\n%s\n", op.first.c_str(), op.second.refs, op.second.stack.c_str()); + } + return ret; + } + + struct handler_alloc_t + { + std::size_t capacity; + std::set> allocations; + }; + // defined in session_impl.cpp + extern std::map _handler_storage; + extern std::mutex _handler_storage_mutex; + extern bool _handler_logger_registered; + + inline void log_handler_allocators() noexcept + { + static char const* const handler_names[] = { + "write_handler", + "read_handler", + "udp_handler", + "tick_handler", + "abort_handler", + "defer_handler", + "utp_handler", + "submit_handler", + }; + std::lock_guard l(_handler_storage_mutex); + std::printf("handler allocator storage:\n\n"); + for (auto const& e : _handler_storage) + { + std::size_t allocated = 0; + std::string handler_name; + // pick the largest allocation, in case the storage was used for + // different handlers + for (auto const& a : e.second.allocations) + { + if (allocated >= a.second) continue; + allocated = a.second; + handler_name = demangle(e.second.allocations.begin()->first->name()); + } + + std::printf("%15s: capacity: %-3d allocated: %-3d handler: %s\n" + , handler_names[e.first], int(e.second.capacity), int(allocated), handler_name.c_str()); + } + } + + template + void record_handler_allocation(int const type, std::size_t const capacity) + { + std::lock_guard l(_handler_storage_mutex); + auto& e = _handler_storage[type]; + e.capacity = capacity; + e.allocations.emplace(&typeid(Handler), sizeof(Handler)); + if (!_handler_logger_registered) + { + std::atexit(&log_handler_allocators); + _handler_logger_registered = true; + } + } +} + +#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/docs/include/libtorrent/disabled_disk_io.hpp b/docs/include/libtorrent/disabled_disk_io.hpp new file mode 100644 index 0000000..4487b92 --- /dev/null +++ b/docs/include/libtorrent/disabled_disk_io.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2016, 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_DISABLED_DISK_IO +#define TORRENT_DISABLED_DISK_IO + +#include "libtorrent/config.hpp" +#include "libtorrent/io_context.hpp" + +#include + +namespace libtorrent { + + struct counters; + struct disk_interface; + struct settings_interface; + + // creates a disk io object that discards all data written to it, and only + // returns zero-buffers when read from. May be useful for testing and + // benchmarking. + TORRENT_EXPORT std::unique_ptr disabled_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); +} + +#endif + diff --git a/docs/include/libtorrent/disk_buffer_holder.hpp b/docs/include/libtorrent/disk_buffer_holder.hpp new file mode 100644 index 0000000..b88de7a --- /dev/null +++ b/docs/include/libtorrent/disk_buffer_holder.hpp @@ -0,0 +1,112 @@ +/* + +Copyright (c) 2008-2009, 2013-2019, Arvid Norberg +Copyright (c) 2018, 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_DISK_BUFFER_HOLDER_HPP_INCLUDED +#define TORRENT_DISK_BUFFER_HOLDER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include + +namespace libtorrent { + + // the interface for freeing disk buffers, used by the disk_buffer_holder. + // when implementing disk_interface, this must also be implemented in order + // to return disk buffers back to libtorrent + struct TORRENT_EXPORT buffer_allocator_interface + { + virtual void free_disk_buffer(char* b) = 0; + protected: + ~buffer_allocator_interface() = default; + }; + + // The disk buffer holder acts like a ``unique_ptr`` that frees a disk buffer + // when it's destructed + // + // If this buffer holder is moved-from, default constructed or reset, + // ``data()`` will return nullptr. + struct TORRENT_EXPORT disk_buffer_holder + { + 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 + , char* buf, int sz) noexcept; + + // default construct a holder that does not own any buffer + disk_buffer_holder() noexcept = default; + + // frees disk buffer held by this object + ~disk_buffer_holder(); + + // return a pointer to the held buffer, if any. Otherwise returns nullptr. + char* data() const noexcept { return m_buf; } + + // free the held disk buffer, if any, and clear the holder. This sets the + // holder object to a default-constructed state + void reset(); + + // swap pointers of two disk buffer holders. + void swap(disk_buffer_holder& h) noexcept + { + using std::swap; + swap(h.m_allocator, m_allocator); + swap(h.m_buf, m_buf); + swap(h.m_size, m_size); + } + + // if this returns true, the buffer may not be modified in place + bool is_mutable() const noexcept { return false; } + + // implicitly convertible to true if the object is currently holding a + // buffer + explicit operator bool() const noexcept { return m_buf != nullptr; } + + std::ptrdiff_t size() const { return m_size; } + + private: + + buffer_allocator_interface* m_allocator = nullptr; + char* m_buf = nullptr; + int m_size = 0; + }; + +} + +#endif diff --git a/docs/include/libtorrent/disk_interface.hpp b/docs/include/libtorrent/disk_interface.hpp new file mode 100644 index 0000000..7d3246c --- /dev/null +++ b/docs/include/libtorrent/disk_interface.hpp @@ -0,0 +1,425 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2018, 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_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" + +// OVERVIEW +// +// The disk I/O can be customized in libtorrent. In previous versions, the +// customization was at the level of each torrent. Now, the customization point +// is at the session level. All torrents added to a session will use the same +// disk I/O subsystem, as determined by the disk_io_constructor (in +// session_params). +// +// This allows the disk subsystem to also customize threading and disk job +// management. +// +// To customize the disk subsystem, implement disk_interface and provide a +// factory function to the session constructor (via session_params). +// +// Example use: +// +// .. include:: ../examples/custom_storage.cpp +// :code: c++ +// :tab-width: 2 +// :start-after: -- example begin +// :end-before: // -- example end +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 locked TORRENT_DEPRECATED = 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. 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; + }; + + using disk_job_flags_t = flags::bitfield_flag; + + // The disk_interface is the customization point for disk I/O in libtorrent. + // implement this interface and provide a factory function to the session constructor + // use custom disk I/O. All functions on the disk subsystem (implementing + // disk_interface) are called from within libtorrent's network thread. For + // disk I/O to be performed in a separate thread, the disk subsystem has to + // manage that itself. + // + // Although the functions are called ``async_*``, they do not technically + // *have* to be asynchronous, but they support being asynchronous, by + // expecting the result passed back into a callback. The callbacks must be + // posted back onto the network thread via the io_context object passed into + // the constructor. The callbacks will be run in the network thread. + struct TORRENT_EXPORT disk_interface + { + // force making a copy of the cached block, rather than getting a + // reference to a block already in the cache. This is used the block is + // expected to be overwritten very soon, by async_write()`, and we need + // access to the previous content. + 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. This is a hint that this block is + // unlikely to be read again anytime soon, and caching it would be + // wasteful. + static constexpr disk_job_flags_t volatile_read = 4_bit; + + // compute a v1 piece hash. This is only used by the async_hash() call. + // If this flag is not set in the async_hash() call, the SHA-1 piece + // hash does not need to be computed. + static constexpr disk_job_flags_t v1_hash = 5_bit; + + // this flag instructs a hash job that we just completed this piece, and + // it should be flushed to disk + static constexpr disk_job_flags_t flush_piece = 7_bit; + + // this is called when a new torrent is added. The shared_ptr can be + // used to hold the internal torrent object alive as long as there are + // outstanding disk operations on the storage. + // The returned storage_holder is an owning reference to the underlying + // storage that was just created. It is fundamentally a storage_index_t + virtual storage_holder new_torrent(storage_params const& p + , std::shared_ptr const& torrent) = 0; + + // remove the storage with the specified index. This is not expected to + // delete any files from disk, just to clean up any resources associated + // with the specified storage. + virtual void remove_torrent(storage_index_t) = 0; + + // perform a read or write operation from/to the specified storage + // index and the specified request. When the operation completes, call + // handler possibly with a disk_buffer_holder, holding the buffer with + // the result. Flags may be set to affect the read operation. See + // disk_job_flags_t. + // + // The disk_observer is a callback to indicate that + // the store buffer/disk write queue is below the watermark to let peers + // start writing buffers to disk again. When ``async_write()`` returns + // ``true``, indicating the write queue is full, the peer will stop + // further writes and wait for the passed-in ``disk_observer`` to be + // notified before resuming. + // + // Note that for ``async_read``, the peer_request (``r``) is not + // necessarily aligned to blocks (but it is most of the time). However, + // all writes (passed to ``async_write``) are guaranteed to be block + // aligned. + 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; + + // Compute hash(es) for the specified piece. Unless the v1_hash flag is + // set (in ``flags``), the SHA-1 hash of the whole piece does not need + // to be computed. + // + // The `v2` span is optional and can be empty, which means v2 hashes + // should not be computed. If v2 is non-empty it must be at least large + // enough to hold all v2 blocks in the piece, and this function will + // fill in the span with the SHA-256 block hashes of the piece. + virtual void async_hash(storage_index_t storage, piece_index_t piece, span v2 + , disk_job_flags_t flags + , std::function handler) = 0; + + // computes the v2 hash (SHA-256) of a single block. The block at + // ``offset`` in piece ``piece``. + virtual void async_hash2(storage_index_t storage, piece_index_t piece, int offset, disk_job_flags_t flags + , std::function handler) = 0; + + // called to request the files for the specified storage/torrent be + // moved to a new location. It is the disk I/O object's responsibility + // to synchronize this with any currently outstanding disk operations to + // the storage. Whether files are replaced at the destination path or + // not is controlled by ``flags`` (see move_flags_t). + virtual void async_move_storage(storage_index_t storage, std::string p, move_flags_t flags + , std::function handler) = 0; + + // This is called on disk I/O objects to request they close all open + // files for the specified storage/torrent. If file handles are not + // pooled/cached, it can be a no-op. For truly asynchronous disk I/O, + // this should provide at least one point in time when all files are + // closed. It is possible that later asynchronous operations will + // re-open some of the files, by the time this completion handler is + // called, that's fine. + virtual void async_release_files(storage_index_t storage + , std::function handler = std::function()) = 0; + + // this is called when torrents are added to validate their resume data + // against the files on disk. This function is expected to do a few things: + // + // if ``links`` is non-empty, it contains a string for each file in the + // torrent. The string being a path to an existing identical file. The + // default behavior is to create hard links of those files into the + // storage of the new torrent (specified by ``storage``). An empty + // string indicates that there is no known identical file. This is part + // of the "mutable torrent" feature, where files can be reused from + // other torrents. + // + // The ``resume_data`` points the resume data passed in by the client. + // + // If the ``resume_data->flags`` field has the seed_mode flag set, all + // files/pieces are expected to be on disk already. This should be + // verified. Not just the existence of the file, but also that it has + // the correct size. + // + // Any file with a piece set in the ``resume_data->have_pieces`` bitmask + // should exist on disk, this should be verified. Pad files and files + // with zero priority may be skipped. + virtual void async_check_files(storage_index_t storage + , add_torrent_params const* resume_data + , aux::vector links + , std::function handler) = 0; + + // This is called when a torrent is stopped. It gives the disk I/O + // object an opportunity to flush any data to disk that's currently kept + // cached. This function should at least do the same thing as + // async_release_files(). + virtual void async_stop_torrent(storage_index_t storage + , std::function handler = std::function()) = 0; + + // This function is called when the name of a file in the specified + // storage has been requested to be renamed. The disk I/O object is + // responsible for renaming the file without racing with other + // potentially outstanding operations against the file (such as read, + // write, move, etc.). + virtual void async_rename_file(storage_index_t storage + , file_index_t index, std::string name + , std::function handler) = 0; + + // This function is called when some file(s) on disk have been requested + // to be removed by the client. ``storage`` indicates which torrent is + // referred to. See session_handle for ``remove_flags_t`` flags + // indicating which files are to be removed. + // e.g. session_handle::delete_files - delete all files + // session_handle::delete_partfile - only delete part file. + virtual void async_delete_files(storage_index_t storage, remove_flags_t options + , std::function handler) = 0; + + // This is called to set the priority of some or all files. Changing the + // priority from or to 0 may involve moving data to and from the + // partfile. The disk I/O object is responsible for correctly + // synchronizing this work to not race with any potentially outstanding + // asynchronous operations affecting these files. + // + // ``prio`` is a vector of the file priority for all files. If it's + // shorter than the total number of files in the torrent, they are + // assumed to be set to the default priority. + virtual void async_set_file_priority(storage_index_t storage + , aux::vector prio + , std::function)> handler) = 0; + + // This is called when a piece fails the hash check, to ensure there are + // no outstanding disk operations to the piece before blocks are + // re-requested from peers to overwrite the existing blocks. The disk I/O + // object does not need to perform any action other than synchronize + // with all outstanding disk operations to the specified piece before + // posting the result back. + virtual void async_clear_piece(storage_index_t storage, piece_index_t index + , std::function handler) = 0; + + // update_stats_counters() is called to give the disk storage an + // opportunity to update gauges in the ``c`` stats counters, that aren't + // updated continuously as operations are performed. This is called + // before a snapshot of the counters are passed to the client. + virtual void update_stats_counters(counters& c) const = 0; + + // Return a list of all the files that are currently open for the + // specified storage/torrent. This is is just used for the client to + // query the currently open files, and which modes those files are open + // in. + virtual std::vector get_status(storage_index_t) const = 0; + + // this is called when the session is starting to shut down. The disk + // I/O object is expected to flush any outstanding write jobs, cancel + // hash jobs and initiate tearing down of any internal threads. If + // ``wait`` is true, this should be asynchronous. i.e. this call should + // not return until all threads have stopped and all jobs have either + // been aborted or completed and the disk I/O object is ready to be + // destructed. + virtual void abort(bool wait) = 0; + + // This will be called after a batch of disk jobs has been issues (via + // the ``async_*`` ). It gives the disk I/O object an opportunity to + // notify any potential condition variables to wake up the disk + // thread(s). The ``async_*`` calls can of course also notify condition + // variables, but doing it in this call allows for batching jobs, by + // issuing the notification once for a collection of jobs. + virtual void submit_jobs() = 0; + + // This is called to notify the disk I/O object that the settings have + // been updated. In the disk io constructor, a settings_interface + // reference is passed in. Whenever these settings are updated, this + // function is called to allow the disk I/O object to react to any + // changed settings relevant to its operations. + virtual void settings_updated() = 0; + + // hidden + virtual ~disk_interface() {} + }; + + // a unique, owning, reference to the storage of a torrent in a disk io + // subsystem (class that implements disk_interface). This is held by the + // internal libtorrent torrent object to tie the storage object allocated + // for a torrent to the lifetime of the internal torrent object. When a + // torrent is removed from the session, this holder is destructed and will + // inform the disk object. + struct TORRENT_EXPORT 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 (&rhs == this) return *this; + 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}; + }; + +} // namespace libtorrent + +#endif diff --git a/docs/include/libtorrent/disk_observer.hpp b/docs/include/libtorrent/disk_observer.hpp new file mode 100644 index 0000000..6f54fe1 --- /dev/null +++ b/docs/include/libtorrent/disk_observer.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2010, 2013-2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, 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_DISK_OBSERVER_HPP +#define TORRENT_DISK_OBSERVER_HPP + +#include "libtorrent/config.hpp" + +namespace libtorrent { + + struct TORRENT_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/docs/include/libtorrent/download_priority.hpp b/docs/include/libtorrent/download_priority.hpp new file mode 100644 index 0000000..144dd3e --- /dev/null +++ b/docs/include/libtorrent/download_priority.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2017-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_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/docs/include/libtorrent/entry.hpp b/docs/include/libtorrent/entry.hpp new file mode 100644 index 0000000..87e0ab6 --- /dev/null +++ b/docs/include/libtorrent/entry.hpp @@ -0,0 +1,334 @@ +/* + +Copyright (c) 2003-2009, 2013-2020, Arvid Norberg +Copyright (c) 2016-2017, 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_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" +#include "libtorrent/aux_/strview_less.hpp" + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + // backwards compatibility + using type_error = system_error; +#endif + struct bdecode_node; + + // 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 = std::map; + 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 + entry(list_type); // NOLINT + entry(integer_type); // NOLINT + entry(preformatted_type); // NOLINT + + // hidden + 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; + } + + // 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. + entry& operator=(bdecode_node const&) &; + entry& operator=(entry const&) &; + entry& operator=(entry&&) & noexcept; + entry& operator=(dictionary_type) &; + entry& operator=(span) &; + entry& operator=(list_type) &; + entry& operator=(integer_type) &; + entry& operator=(preformatted_type) &; + + // hidden + 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; + } + + // 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(); + integer_type const& integer() const; + string_type& string(); + string_type const& string() const; + list_type& list(); + list_type const& list() const; + dictionary_type& dict(); + dictionary_type const& dict() const; + preformatted_type& preformatted(); + preformatted_type const& 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); + entry const& 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: + // hidden + // 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 aux { + + // internal + TORRENT_EXPORT string_view integer_to_str(std::array& 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/docs/include/libtorrent/enum_net.hpp b/docs/include/libtorrent/enum_net.hpp new file mode 100644 index 0000000..c45f8d1 --- /dev/null +++ b/docs/include/libtorrent/enum_net.hpp @@ -0,0 +1,234 @@ +/* + +Copyright (c) 2007-2008, 2010, 2014-2020, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +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_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_context.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 { + + // internal +using interface_flags = flags::bitfield_flag; + +namespace if_flags { + + // internal + 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; +} + +// internal +enum class if_state : std::uint8_t { + + up, + dormant, + lowerlayerdown, + down, + notpresent, + testing, + unknown +}; + +// internal + 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; + }; + +// internal + 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_context& ios + , error_code& ec); + + TORRENT_EXTRA_EXPORT std::vector enum_routes(io_context& 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); + + // returns whether there are *any* routes to the internet in the routing + // table. This can be used to determine if the routing table is fully + // populated or not. + TORRENT_EXTRA_EXPORT bool has_any_internet_route(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_context& 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_context& ios, error_code& ec); + +} + +#endif diff --git a/docs/include/libtorrent/error.hpp b/docs/include/libtorrent/error.hpp new file mode 100644 index 0000000..939d101 --- /dev/null +++ b/docs/include/libtorrent/error.hpp @@ -0,0 +1,53 @@ +/* + +Copyright (c) 2009, 2017, 2019, Arvid Norberg +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_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/docs/include/libtorrent/error_code.hpp b/docs/include/libtorrent/error_code.hpp new file mode 100644 index 0000000..2f87f2b --- /dev/null +++ b/docs/include/libtorrent/error_code.hpp @@ -0,0 +1,598 @@ +/* + +Copyright (c) 2008-2011, 2013-2020, Arvid Norberg +Copyright (c) 2016-2017, 2019, Steven Siloti +Copyright (c) 2018, 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_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, + invalid_hash_request, + invalid_hashes, + invalid_hash_reject, + +#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, + // skipped announce (because it's assumed to be unreachable over the + // given source network interface) + announce_skipped, + +#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, + // blocked by SSRF mitigation + ssrf_mitigation, + // blocked because IDNA host names are banned + blocked_by_idna, + + // the torrent file has an unknown meta version + torrent_unknown_version = 210, + // the v2 torrent file has no file tree + torrent_missing_file_tree, + // the torrent contains v2 keys but does not specify meta version 2 + torrent_missing_meta_version, + // the v1 and v2 file metadata does not match + torrent_inconsistent_files, + // one or more files are missing piece layer hashes + torrent_missing_piece_layer, + // a piece layer has the wrong size or failed hash check + torrent_invalid_piece_layer, + // a v2 file entry has no root hash + torrent_missing_pieces_root, + // the v1 and v2 hashes do not describe the same data + torrent_inconsistent_hashes, + // a file in the v2 metadata has the pad attribute set + torrent_invalid_pad_file, + + // 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) {} + storage_error(error_code e, operation_t const op) + : ec(e), file_idx(-1), operation(op) {} + + // 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); } + + private: + // internal + std::int32_t file_idx:24; + + public: + + // 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. + TORRENT_DEPRECATED + char const* operation_str() const + { return operation_name(operation); } +#endif + }; + + // internal + std::string print_error(error_code const&); +} // namespace libtorrent + +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/docs/include/libtorrent/extensions.hpp b/docs/include/libtorrent/extensions.hpp new file mode 100644 index 0000000..708c2ed --- /dev/null +++ b/docs/include/libtorrent/extensions.hpp @@ -0,0 +1,548 @@ +/* + +Copyright (c) 2006-2007, 2011, 2013-2020, Arvid Norberg +Copyright (c) 2014-2019, Steven Siloti +Copyright (c) 2016, Alden Torres +Copyright (c) 2018, Greg Hazel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 +// +// 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&, client_data_t); +// +// 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; + +TORRENT_VERSION_NAMESPACE_3 + + // 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 client_data_t 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&, client_data_t) + { return std::shared_ptr(); } + + // called when plugin is added to a session + virtual void added(session_handle const&) {} + + // called when the session is aborted + // the plugin should perform any cleanup necessary to allow the session's + // destruction (e.g. cancel outstanding async operations) + virtual void abort() {} + + // 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(info_hash_t 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)(); } + +#if TORRENT_ABI_VERSION <= 2 + // called when saving settings state + virtual void save_state(entry&) {} + + // called when loading settings state + virtual void load_state(bdecode_node const&) {} +#endif + + virtual std::map save_state() const { return {}; } + + // called on startup while loading settings state from the session_params + virtual void load_state(std::map const&) {} + }; + +TORRENT_VERSION_NAMESPACE_3_END + + 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; } + + virtual void sent_have_all() {} + virtual void sent_have_none() {} + virtual void sent_reject_request(peer_request const&) {} + virtual void sent_allow_fast(piece_index_t) {} + virtual void sent_suggest(piece_index_t) {} + virtual void sent_cancel(peer_request const&) {} + virtual void sent_request(peer_request const&) {} + virtual void sent_choke() {} + // called after a choke message has been sent to the peer + virtual void sent_unchoke() {} + virtual void sent_interested() {} + virtual void sent_not_interested() {} + virtual void sent_have(piece_index_t) {} + virtual void sent_piece(peer_request const&) {} + + // 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/docs/include/libtorrent/file.hpp b/docs/include/libtorrent/file.hpp new file mode 100644 index 0000000..d2acf99 --- /dev/null +++ b/docs/include/libtorrent/file.hpp @@ -0,0 +1,109 @@ +/* + +Copyright (c) 2004, 2008-2010, 2014-2020, Arvid Norberg +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_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/aux_/open_mode.hpp" +#include "libtorrent/flags.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#ifdef TORRENT_WINDOWS +// windows part +#include "libtorrent/aux_/windows.hpp" +#include +#include +#else +// posix part + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#include +#include +#include +#include +#include // for DIR + +#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 + + struct TORRENT_EXTRA_EXPORT file + { + file(); + file(std::string const& p, aux::open_mode_t m, error_code& ec); + file(file&&) noexcept; + file& operator=(file&&); + ~file(); + + file(file const&) = delete; + file& operator=(file const&) = delete; + + std::int64_t writev(std::int64_t file_offset, span bufs + , error_code& ec, aux::open_mode_t flags = {}); + std::int64_t readv(std::int64_t file_offset, span bufs + , error_code& ec, aux::open_mode_t flags = {}); + + private: + handle_type m_file_handle; + }; +} + +#endif // TORRENT_FILE_HPP_INCLUDED diff --git a/docs/include/libtorrent/file_storage.hpp b/docs/include/libtorrent/file_storage.hpp new file mode 100644 index 0000000..bfb31c6 --- /dev/null +++ b/docs/include/libtorrent/file_storage.hpp @@ -0,0 +1,722 @@ +/* + +Copyright (c) 2008-2010, 2012-2020, Arvid Norberg +Copyright (c) 2016, 2020, Alden Torres +Copyright (c) 2017, 2019, 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_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" +#include "libtorrent/error_code.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/disk_interface.hpp" // for default_block_size +#include "libtorrent/fwd.hpp" + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + // information about a file in a file_storage + struct TORRENT_DEPRECATED_EXPORT file_entry + { +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + // 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&&) & = default; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // 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 + +namespace aux { + struct path_index_tag; + using path_index_t = aux::strong_typedef; + + // internal + struct file_entry + { + friend class ::lt::file_storage; + file_entry(); + file_entry(file_entry const& fe); + file_entry& operator=(file_entry const& fe) &; + file_entry(file_entry&& fe) noexcept; + file_entry& operator=(file_entry&& fe) & noexcept; + ~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, + }; + + static constexpr aux::path_index_t no_path{(1 << 30) - 1}; + static constexpr aux::path_index_t path_is_absolute{(1 << 30) - 2}; + + // 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 = nullptr; + public: + // the SHA-256 root of the merkle tree for this file + // this is a pointer into the .torrent file + char const* root = nullptr; + + // 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: + // no_path means no path (i.e. single file torrent) + // path_is_absolute means the filename + // in this field contains the full, absolute path + // to the file + aux::path_index_t path_index = file_entry::no_path; + }; + +} // aux namespace + + // 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 + { + 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&&) &; + + // internal limitations restrict file sizes to not be larger than this + // We use int to index into file merkle trees, so a file may not contain more + // than INT_MAX entries. That means INT_MAX / 2 blocks (leafs) in each + // tree. + static constexpr std::int64_t max_file_size = (std::min)( + (std::int64_t(1) << 48) - 1 + , std::int64_t((std::numeric_limits::max)() / 2) * default_block_size); + static constexpr std::int64_t max_file_offset = (std::int64_t(1) << 48) - 1; + + // 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; + TORRENT_DEPRECATED static constexpr file_flags_t pad_file = 0_bit; + TORRENT_DEPRECATED static constexpr file_flags_t attribute_hidden = 1_bit; + TORRENT_DEPRECATED static constexpr file_flags_t attribute_executable = 2_bit; + TORRENT_DEPRECATED static constexpr file_flags_t 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. + // + // ``root_hash`` is an optional pointer to a 32 byte SHA-256 hash, being + // the merkle tree root hash for this file. This is only used for v2 + // torrents. If the ``root hash`` is specified for one file, it has to + // be specified for all, otherwise this function will fail. + // Note that the buffer ``root_hash`` points to must out-live the + // file_storage object, it will not be copied. This parameter is only + // used when *loading* torrents, that already have their file hashes + // computed. When creating torrents, the file hashes will be computed by + // the piece hashes. + // + // 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``. + // + // The overloads that take an `error_code` reference will report failures + // via that variable, otherwise `system_error` is thrown. +#ifndef BOOST_NO_EXCEPTIONS + 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() + , char const* root_hash = nullptr); + 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() + , char const* root_hash = nullptr); +#endif // BOOST_NO_EXCEPTIONS + void add_file_borrow(error_code& ec, 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() + , char const* root_hash = nullptr); + void add_file(error_code& ec, 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() + , char const* root_hash = nullptr); + + // 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 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + 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 functions depending on aux::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 + aux::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; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#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 + , std::int64_t 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. It must be a power of two + // and at least 16 kiB. + 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; + + // Returns the size of the given piece. If the piece spans multiple files, + // only the first file is considered part of the piece. This is used for + // v2 torrents, where all files are piece aligned and padded. i.e. The pad + // files are not considered part of the piece for this purpose. + int piece_size2(piece_index_t index) const; + + // returns the number of blocks in the specified piece, for v2 torrents. + int blocks_in_piece2(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; + + // arrange files and padding to match the canonical form required + // by BEP 52 + void canonicalize(); + + // 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. + // + // ``root()`` returns the SHA-256 merkle tree root of the specified file, + // in case this is a v2 torrent. Otherwise returns zeros. + // ``root_ptr()`` returns a pointer to the SHA-256 merkle tree root hash + // for the specified file. The pointer points into storage referred to + // when the file was added, it is not owned by this object. Torrents + // that are not v2 torrents return nullptr. + // + // 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; + sha256_hash root(file_index_t index) const; + char const* root_ptr(file_index_t const index) const; + std::string 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 number of pieces or blocks the file at `index` spans, + // under the assumption that the file is aligned to the start of a piece. + // This is only meaningful for v2 torrents, where files are guaranteed + // such alignment. + // These numbers are used to size and navigate the merkle hash tree for + // each file. + int file_num_pieces(file_index_t index) const; + int file_num_blocks(file_index_t index) const; + index_range file_piece_range(file_index_t) const; + + // index of first piece node in the merkle tree + int file_first_piece_node(file_index_t index) const; + int file_first_block_node(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; + + // internal + // 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. + aux::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; + file_index_t file_index_at_piece(piece_index_t piece) const; + + // finds the file with the given root hash and returns its index + // if there is no file with the root hash, file_index_t{-1} is returned + file_index_t file_index_for_root(sha256_hash const& root_hash) const; + + // returns the piece index the given file starts at + piece_index_t piece_index_at_file(file_index_t f) const; + +#if TORRENT_USE_INVARIANT_CHECKS + // internal + bool owns_name(file_index_t const f) const + { return m_files[f].name_len == aux::file_entry::name_is_owned; } +#endif + +#if TORRENT_ABI_VERSION <= 2 + // 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``. + TORRENT_DEPRECATED + char const* file_name_ptr(file_index_t index) const; + TORRENT_DEPRECATED + int file_name_len(file_index_t index) const; +#endif + +#if TORRENT_ABI_VERSION == 1 + // these were deprecated in 1.0. Use the versions that take an index instead + TORRENT_DEPRECATED + sha1_hash hash(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::string symlink(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::time_t mtime(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + int file_index(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::string file_path(aux::file_entry const& fe, std::string const& save_path = "") const; + TORRENT_DEPRECATED + std::string file_name(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::int64_t file_size(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + bool pad_file_at(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::int64_t file_offset(aux::file_entry const& fe) const; +#endif + + // 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(); + + // returns true if this torrent contains v2 metadata. + bool v2() const { return m_v2; } + + // internal + // this is an optimization for create_torrent + std::string const& internal_symlink(file_index_t index) const; + + // internal + void remove_tail_padding(); + + private: + + std::string internal_file_path(file_index_t index) const; + file_index_t last_file() const noexcept; + + aux::path_index_t get_or_add_path(string_view path); + + // the number of bytes in a regular piece + // (i.e. not the potentially truncated last piece) + int m_piece_length = 0; + + // the number of pieces in the torrent + int m_num_pieces = 0; + + // whether this is a v2 torrent or not. Additional requirements apply to + // v2 torrents + bool m_v2 = false; + + void update_path_index(aux::file_entry& e, std::string const& path + , bool set_name = true); + + // 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 aux::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 aux::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 = 0; + }; + +namespace aux { + + TORRENT_EXTRA_EXPORT + int calc_num_pieces(file_storage const& fs); + + // this is used when loading v2 torrents that are backwards compatible with + // v1 torrents. Both v1 and v2 structures must describe the same file layout, + // this compares the two. + TORRENT_EXTRA_EXPORT + bool files_compatible(file_storage const& lhs, file_storage const& rhs); + + // 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); + + TORRENT_EXTRA_EXPORT + std::int64_t size_on_disk(file_storage const& fs); + +} // namespace aux +} // namespace libtorrent + +#endif // TORRENT_FILE_STORAGE_HPP_INCLUDED diff --git a/docs/include/libtorrent/fingerprint.hpp b/docs/include/libtorrent/fingerprint.hpp new file mode 100644 index 0000000..cadde82 --- /dev/null +++ b/docs/include/libtorrent/fingerprint.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2003, 2006, 2009, 2013, 2016-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_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/docs/include/libtorrent/flags.hpp b/docs/include/libtorrent/flags.hpp new file mode 100644 index 0000000..27e9ef9 --- /dev/null +++ b/docs/include/libtorrent/flags.hpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2017-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_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; + +#if TORRENT_USE_IOSTREAM + friend std::ostream& operator<<(std::ostream& os, bitfield_flag val) + { return os << static_cast(val); } +#endif + +private: + UnderlyingType m_val; +}; + +} // flags +} // libtorrent + +#endif diff --git a/docs/include/libtorrent/fwd.hpp b/docs/include/libtorrent/fwd.hpp new file mode 100644 index 0000000..90eb65d --- /dev/null +++ b/docs/include/libtorrent/fwd.hpp @@ -0,0 +1,319 @@ +/* + +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2017-2021, Arvid Norberg +Copyright (c) 2020, 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_FWD_HPP +#define TORRENT_FWD_HPP + +#include "libtorrent/config.hpp" + +namespace libtorrent { + +// include/libtorrent/add_torrent_params.hpp +TORRENT_VERSION_NAMESPACE_3 +struct add_torrent_params; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/alert.hpp +struct alert; + +// include/libtorrent/alert_types.hpp +struct dht_routing_bucket; +TORRENT_VERSION_NAMESPACE_3 +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 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; +struct file_prio_alert; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/announce_entry.hpp +TORRENT_VERSION_NAMESPACE_2 +struct announce_infohash; +struct announce_endpoint; +struct announce_entry; +TORRENT_VERSION_NAMESPACE_2_END + +// include/libtorrent/bdecode.hpp +struct bdecode_node; + +// include/libtorrent/bitfield.hpp +struct bitfield; + +// include/libtorrent/client_data.hpp +struct client_data_t; + +// include/libtorrent/create_torrent.hpp +struct create_torrent; + +// include/libtorrent/disk_buffer_holder.hpp +struct buffer_allocator_interface; +struct disk_buffer_holder; + +// include/libtorrent/disk_interface.hpp +struct open_file_state; +struct disk_interface; +struct storage_holder; + +// include/libtorrent/disk_observer.hpp +struct disk_observer; + +// include/libtorrent/entry.hpp +class entry; + +// include/libtorrent/error_code.hpp +struct storage_error; + +// include/libtorrent/extensions.hpp +TORRENT_VERSION_NAMESPACE_3 +struct plugin; +TORRENT_VERSION_NAMESPACE_3_END +struct torrent_plugin; +struct peer_plugin; +struct crypto_plugin; + +// include/libtorrent/file_storage.hpp +struct file_slice; +class file_storage; + +// include/libtorrent/hasher.hpp +TORRENT_CRYPTO_NAMESPACE +class hasher; +class hasher256; +TORRENT_CRYPTO_NAMESPACE_END + +// include/libtorrent/info_hash.hpp +struct info_hash_t; + +// include/libtorrent/ip_filter.hpp +struct ip_filter; +class port_filter; + +// 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/performance_counters.hpp +struct counters; + +// include/libtorrent/piece_block.hpp +struct piece_block; + +// include/libtorrent/session.hpp +struct session_proxy; +struct session; + +// include/libtorrent/session_handle.hpp +struct session_handle; + +// include/libtorrent/session_params.hpp +TORRENT_VERSION_NAMESPACE_3 +struct session_params; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/session_stats.hpp +struct stats_metric; + +// include/libtorrent/settings_pack.hpp +struct settings_interface; +struct settings_pack; + +// include/libtorrent/storage_defs.hpp +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; +struct load_torrent_limits; +TORRENT_VERSION_NAMESPACE_3 +class torrent_info; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/torrent_status.hpp +TORRENT_VERSION_NAMESPACE_3 +struct torrent_status; +TORRENT_VERSION_NAMESPACE_3_END + +#if TORRENT_ABI_VERSION <= 2 + +// include/libtorrent/alert_types.hpp +TORRENT_VERSION_NAMESPACE_3 +struct torrent_added_alert; +struct stats_alert; +struct anonymous_mode_alert; +struct mmap_cache_alert; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/file_storage.hpp +struct file_entry; + +// include/libtorrent/fingerprint.hpp +struct fingerprint; + +// include/libtorrent/kademlia/dht_settings.hpp +namespace dht { +struct dht_settings; +} + +// include/libtorrent/session_settings.hpp +struct pe_settings; + +// include/libtorrent/session_status.hpp +struct utp_status; +struct session_status; + +#endif // TORRENT_ABI_VERSION + +} + +namespace lt = libtorrent; + +#endif // TORRENT_FWD_HPP diff --git a/docs/include/libtorrent/gzip.hpp b/docs/include/libtorrent/gzip.hpp new file mode 100644 index 0000000..f8f96c1 --- /dev/null +++ b/docs/include/libtorrent/gzip.hpp @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2008-2009, 2014-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_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& ec); + + // 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 gzip_errors + +} // namespace libtorrent + +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/docs/include/libtorrent/hash_picker.hpp b/docs/include/libtorrent/hash_picker.hpp new file mode 100644 index 0000000..049d46f --- /dev/null +++ b/docs/include/libtorrent/hash_picker.hpp @@ -0,0 +1,234 @@ +/* + +Copyright (c) 2017, BitTorrent Inc. +Copyright (c) 2019, Steven Siloti +Copyright (c) 2019-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_HASH_PICKER_HPP_INCLUDED +#define TORRENT_HASH_PICKER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/merkle_tree.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/time.hpp" +#include +#include + +namespace libtorrent +{ + struct torrent_peer; + + struct set_block_hash_result + { + enum class result + { + // hash is verified + success, + // hash cannot be verified yet + unknown, + // hash conflict in leaf node + block_hash_failed, + // hash conflict in a parent node + piece_hash_failed + }; + + explicit set_block_hash_result(result s) : status(s), first_verified_block(0), num_verified(0) {} + set_block_hash_result(int first_block, int num) : status(result::success), first_verified_block(first_block), num_verified(num) {} + + static set_block_hash_result unknown() { return set_block_hash_result(result::unknown); } + static set_block_hash_result block_hash_failed() { return set_block_hash_result(result::block_hash_failed); } + static set_block_hash_result piece_hash_failed() { return set_block_hash_result(result::piece_hash_failed); } + + result status; + // if status is success, this will hold the index of the first verified + // block hash as an offset from the index of the first block in the piece + int first_verified_block; + int num_verified; + }; + + struct add_hashes_result + { + explicit add_hashes_result(bool const v) : valid(v) {} + + bool valid; + // the vector contains the block indices (within the piece) that failed + // the hash check + std::vector>> hash_failed; + std::vector hash_passed; + }; + + struct node_index + { + node_index(file_index_t f, std::int32_t n) : file(f), node(n) {} + bool operator==(node_index const& o) const { return file == o.file && node == o.node; } + file_index_t file; + std::int32_t node; + }; + + // the hash request represents a range of hashes in the merkle hash tree for + // a specific file ('file'). + struct TORRENT_EXTRA_EXPORT hash_request + { + hash_request() = default; + hash_request(file_index_t const f, int const b, int const i, int const c, int const p) + : file(f), base(b), index(i), count(c), proof_layers(p) + {} + + hash_request(hash_request const&) = default; + hash_request& operator=(hash_request const& o) = default; + + bool operator==(hash_request const& o) const + { + return file == o.file && base == o.base && index == o.index && count == o.count + && proof_layers == o.proof_layers; + } + + file_index_t file{0}; + // indicates which *level* of the tree we're referring to. 0 means the + // leaf level. + int base = 0; + // the index of the first hash at the specified level. + int index = 0; + // the number of hashes in the range + int count = 0; + int proof_layers = 0; + }; + + // validates the hash_request, to ensure its invariant as well as matching + // the torrent's file_storage and the number of hashes accompanying the + // request + TORRENT_EXTRA_EXPORT + bool validate_hash_request(hash_request const& hr, file_storage const& fs); + + class TORRENT_EXTRA_EXPORT hash_picker + { + public: + hash_picker(file_storage const& files + , aux::vector& trees); + + hash_request pick_hashes(typed_bitfield const& pieces); + + add_hashes_result add_hashes(hash_request const& req, span hashes); + // TODO: support batched adding of block hashes for reduced overhead? + set_block_hash_result set_block_hash(piece_index_t piece, int offset, sha256_hash const& h); + void hashes_rejected(hash_request const& req); + void verify_block_hashes(piece_index_t index); + + // do we know the piece layer hash for a piece + bool have_hash(piece_index_t index) const; + // do we know all the block hashes for a file? + bool have_all(file_index_t file) const; + bool have_all() const; + bool piece_verified(piece_index_t piece) const; + + int piece_layer() const { return m_piece_layer; } + + private: + // returns the number of proof layers needed to verify the node's hash + int layers_to_verify(node_index idx) const; + int file_num_layers(file_index_t idx) const; + + struct piece_hash_request + { + time_point last_request = min_time(); + int num_requests = 0; + bool have = false; + }; + + struct priority_block_request + { + priority_block_request(file_index_t const f, int const b) + : file(f), block(b) {} + file_index_t file; + int block; + int num_requests = 0; + bool operator==(priority_block_request const& o) const + { return file == o.file && block == o.block; } + bool operator!=(priority_block_request const& o) const + { return !(*this == o); } + bool operator<(priority_block_request const& o) const + { return num_requests < o.num_requests; } + }; + + struct piece_block_request + { + piece_block_request(file_index_t const f, piece_index_t::diff_type const p) : file(f), piece(p) {} + file_index_t file; + // the piece from the start of the file + piece_index_t::diff_type piece; + time_point last_request = min_time(); + int num_requests = 0; + bool operator==(piece_block_request const& o) const + { return file == o.file && piece == o.piece; } + bool operator!=(piece_block_request const& o) const + { return !(*this == o); } + bool operator<(piece_block_request const& o) const + { return num_requests < o.num_requests; } + }; + + file_storage const& m_files; + aux::vector& m_merkle_trees; + + // information about every 512-piece span of each file. We request hashes + // for 512 pieces at a time + aux::vector, file_index_t> m_piece_hash_requested; + + // this is for a future per-block request feature +#if 0 + // blocks are only added to this list if there is a time critial block which + // has been downloaded but we don't have its hash or if the initial request + // for the hash was rejected + // this block hash will be requested from every peer possible until the hash + // is received + // the vector is sorted by the number of requests sent for each block + aux::vector m_priority_block_requests; +#endif + + // when a piece fails hash check a request is queued to download the piece's + // block hashes + aux::vector m_piece_block_requests; + + // this is the number of tree levels in a piece. if the piece size is 16 + // kiB, this is 0, since there is no tree per piece. If the piece size is + // 32 kiB, it's 1, and so on. + int const m_piece_layer; + + // this is the number of tree layers for a 512-piece range, which is + // the granularity with which we send hash requests. The number of layers + // all the way down the the block level. + int const m_piece_tree_root_layer; + }; +} // namespace libtorrent + +#endif // TORRENT_HASH_PICKER_HPP_INCLUDED diff --git a/docs/include/libtorrent/hasher.hpp b/docs/include/libtorrent/hasher.hpp new file mode 100644 index 0000000..cf38835 --- /dev/null +++ b/docs/include/libtorrent/hasher.hpp @@ -0,0 +1,185 @@ +/* + +Copyright (c) 2003-2004, 2007, 2009, 2012-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017, 2019, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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_CNG +#include "libtorrent/aux_/win_cng.hpp" + +#elif TORRENT_USE_CRYPTOAPI +#include "libtorrent/aux_/win_crypto_provider.hpp" + +#if !TORRENT_USE_CRYPTOAPI_SHA_512 +#include "libtorrent/sha256.hpp" +#endif + +#elif defined TORRENT_USE_LIBCRYPTO + +extern "C" { +#include +} + +#else +#include "libtorrent/sha1.hpp" +#include "libtorrent/sha256.hpp" +#endif +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { +TORRENT_CRYPTO_NAMESPACE + + // 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_CNG + aux::cng_hash 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 + }; + + class TORRENT_EXPORT hasher256 + { + public: + hasher256(); + + // this is the same as default constructing followed by a call to + // ``update(data, len)``. + hasher256(char const* data, int len); + explicit hasher256(span data); + hasher256(hasher256 const&); + hasher256& operator=(hasher256 const&) &; + + // append the following bytes to what is being hashed + hasher256& update(span data); + hasher256& update(char const* data, int len); + + // returns the SHA-1 digest of the buffers previously passed to + // update() and the hasher constructor. + sha256_hash final(); + + // restore the hasher state to be as if the hasher has just been + // default constructed. + void reset(); + + ~hasher256(); + + private: +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_hd_t m_context; +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA256_CTX m_context; +#elif TORRENT_USE_CNG + aux::cng_hash m_context; +#elif TORRENT_USE_CRYPTOAPI_SHA_512 + aux::crypt_hash m_context; +#elif defined TORRENT_USE_LIBCRYPTO + SHA256_CTX m_context; +#else + sha256_ctx m_context; +#endif + }; + +TORRENT_CRYPTO_NAMESPACE_END +} + +#endif // TORRENT_HASHER_HPP_INCLUDED diff --git a/docs/include/libtorrent/hex.hpp b/docs/include/libtorrent/hex.hpp new file mode 100644 index 0000000..140f944 --- /dev/null +++ b/docs/include/libtorrent/hex.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2004, 2009, 2013, 2015-2020, Arvid Norberg +Copyright (c) 2020, 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_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 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 + +} // namespace aux + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // 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); } + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif +} // namespace libtorrent + +#endif // TORRENT_HEX_HPP_INCLUDED diff --git a/docs/include/libtorrent/http_connection.hpp b/docs/include/libtorrent/http_connection.hpp new file mode 100644 index 0000000..745d5ed --- /dev/null +++ b/docs/include/libtorrent/http_connection.hpp @@ -0,0 +1,257 @@ +/* + +Copyright (c) 2007-2020, Arvid Norberg +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016-2017, 2020, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 + +#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/aux_/resolver_interface.hpp" +#include "libtorrent/optional.hpp" +#include "libtorrent/ssl.hpp" + +namespace libtorrent { + +struct http_connection; +namespace aux { struct resolver_interface; } + +struct close_visitor; + +// 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&)>; +using hostname_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 +{ + friend struct close_visitor; + + http_connection(io_context& ios + , aux::resolver_interface& resolver + , http_handler handler + , bool bottled + , int max_bottled_buffer_size + , http_connect_handler ch + , http_filter_handler fh + , hostname_filter_handler hfh +#if TORRENT_USE_SSL + , 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
    () + , aux::resolver_flags resolve_flags = aux::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
    () + , aux::resolver_flags resolve_flags = aux::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; } + + std::string const& url() const { return m_url; } + +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; + io_context& m_ios; + + 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; + + boost::optional m_sock; + +#if TORRENT_USE_SSL + ssl::context* m_ssl_ctx; +#endif + +#if TORRENT_USE_I2P + i2p_connection* m_i2p_conn; +#endif + aux::resolver_interface& m_resolver; + + http_parser m_parser; + http_handler m_handler; + http_connect_handler m_connect_handler; + http_filter_handler m_filter_handler; + hostname_filter_handler m_hostname_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 + aux::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/docs/include/libtorrent/http_parser.hpp b/docs/include/libtorrent/http_parser.hpp new file mode 100644 index 0000000..f26faa5 --- /dev/null +++ b/docs/include/libtorrent/http_parser.hpp @@ -0,0 +1,168 @@ +/* + +Copyright (c) 2008-2017, 2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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" +#include "libtorrent/aux_/strview_less.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/docs/include/libtorrent/http_seed_connection.hpp b/docs/include/libtorrent/http_seed_connection.hpp new file mode 100644 index 0000000..55ab891 --- /dev/null +++ b/docs/include/libtorrent/http_seed_connection.hpp @@ -0,0 +1,114 @@ +/* + +Copyright (c) 2008-2010, 2012, 2014-2020, Arvid Norberg +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_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 { + + struct torrent; + struct peer_request; + + class TORRENT_EXTRA_EXPORT http_seed_connection + : public web_connection_base + { + friend struct 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& 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/docs/include/libtorrent/http_stream.hpp b/docs/include/libtorrent/http_stream.hpp new file mode 100644 index 0000000..97bbafd --- /dev/null +++ b/docs/include/libtorrent/http_stream.hpp @@ -0,0 +1,230 @@ +/* + +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2007, 2010, 2015, 2019-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_HTTP_STREAM_HPP_INCLUDED +#define TORRENT_HTTP_STREAM_HPP_INCLUDED + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for base64encode +#include "libtorrent/socket_io.hpp" // for print_endpoint + +namespace libtorrent { + +class http_stream : public proxy_base +{ +public: + + explicit http_stream(io_context& io_context) + : proxy_base(io_context) + , 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 + + m_resolver.async_resolve(m_hostname, to_string(m_port).data(), wrap_allocator( + [this](error_code const& ec, tcp::resolver::results_type ips, Handler hn) { + name_lookup(ec, std::move(ips), std::move(hn)); + }, std::move(handler))); + } + +private: + + template + void name_lookup(error_code const& e, tcp::resolver::results_type ips + , Handler h) + { + if (handle_error(e, h)) return; + + auto i = ips.begin(); + m_sock.async_connect(i->endpoint(), wrap_allocator( + [this](error_code const& ec, Handler hn) { + connected(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connected(error_code const& e, Handler h) + { + if (handle_error(e, h)) return; + + using namespace libtorrent::aux; + + if (m_no_connect) + { + std::vector().swap(m_buffer); + std::move(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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake1(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake1(error_code const& e, Handler 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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake2(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake2(error_code const& e, Handler 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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake2(ec, std::move(hn)); + }, std::move(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/docs/include/libtorrent/http_tracker_connection.hpp b/docs/include/libtorrent/http_tracker_connection.hpp new file mode 100644 index 0000000..0c775af --- /dev/null +++ b/docs/include/libtorrent/http_tracker_connection.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2004, 2006-2009, 2012, 2014-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, 2018, 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_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_context& ios + , tracker_manager& man + , tracker_request 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); + bool on_filter_hostname(http_connection& c, string_view hostname); + 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; + io_context& m_ioc; + }; + + TORRENT_EXTRA_EXPORT tracker_response parse_tracker_response( + span data, error_code& ec + , tracker_request_flags_t 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/docs/include/libtorrent/i2p_stream.hpp b/docs/include/libtorrent/i2p_stream.hpp new file mode 100644 index 0000000..2f201eb --- /dev/null +++ b/docs/include/libtorrent/i2p_stream.hpp @@ -0,0 +1,624 @@ +/* + +Copyright (c) 2009, 2013-2019, Arvid Norberg +Copyright (c) 2016-2017, 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_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" +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/hex.hpp" // for to_hex +#include "libtorrent/debug.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); +} +} + +namespace boost { +namespace system { + +template<> +struct is_error_code_enum +{ static const bool value = true; }; + +} +} + + +namespace libtorrent { + + // 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 + +struct i2p_stream : proxy_base +{ + explicit i2p_stream(io_context& io_context); + i2p_stream(i2p_stream&&) noexcept = default; +#if TORRENT_USE_ASSERTS + ~i2p_stream(); +#endif + // explicitly disallow assignment, to silence msvc warning + i2p_stream& operator=(i2p_stream const&) = delete; + + enum command_t : std::uint8_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 h) + { + // 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) + + m_resolver.async_resolve(m_hostname, to_string(m_port).data(), wrap_allocator( + [this](error_code const& ec, tcp::resolver::results_type ips, Handler hn) { + do_connect(ec, std::move(ips), std::move(hn)); + }, std::move(h))); + } + + std::string name_lookup() const { return m_name_lookup; } + void set_name_lookup(char const* name) { m_name_lookup = name; } + + template + void send_name_lookup(Handler 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)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + +private: + + template + void do_connect(error_code const& e, tcp::resolver::results_type ips, Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + if (e || ips.empty()) + { + h(e); + error_code ec; + close(ec); + return; + } + + auto i = ips.begin(); + ADD_OUTSTANDING_ASYNC("i2p_stream::connected"); + m_sock.async_connect(i->endpoint(), wrap_allocator( + [this](error_code const& ec, Handler hn) { + connected(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connected(error_code const& e, Handler 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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void start_read_line(error_code const& e, Handler 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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void read_line(error_code const& e, Handler 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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + + async_read(m_sock, boost::asio::buffer(&m_buffer[read_pos], 1), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + read_line(ec, std::move(hn)); + }, 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), wrap_allocator( + [this](error_code const& err, std::size_t, Handler hn) { + read_line(err, std::move(hn)); + }, std::move(h))); + break; + } + } + + template + void send_connect(Handler 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)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void send_accept(Handler 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)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void send_session_create(Handler 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)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + + // send and receive buffer + aux::noexcept_movable> m_buffer; + char const* m_id; + std::string m_dest; + std::string m_name_lookup; + + enum state_t : std::uint8_t + { + read_hello_response, + read_connect_response, + read_accept_response, + read_session_create_response, + read_name_lookup_response + }; + + command_t m_command; + state_t m_state; +#if TORRENT_USE_ASSERTS + int m_magic; +#endif +}; + +class i2p_connection +{ +public: + explicit i2p_connection(io_context& ios); + ~i2p_connection(); + // explicitly disallow assignment, to silence msvc warning + i2p_connection& operator=(i2p_connection const&) = delete; + + aux::proxy_settings proxy() const; + + bool is_open() const + { + return m_sam_socket + && m_sam_socket->is_open() + && m_state != sam_connecting; + } + template + void open(std::string const& hostname, int port, Handler handler) + { + // we already seem to have a session to this SAM router + if (m_hostname == hostname + && m_port == port + && m_sam_socket + && (is_open() || m_state == sam_connecting)) return; + + m_hostname = hostname; + 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 = std::make_shared(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(), wrap_allocator( + [this,s=m_sam_socket](error_code const& ec, Handler hn) { + on_sam_connect(ec, s, std::move(hn)); + }, std::move(handler))); + } + 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; } + + template + void async_name_lookup(char const* name, 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)); + } + +private: + + template + void on_sam_connect(error_code const& ec, std::shared_ptr, Handler h) + { + COMPLETE_ASYNC("i2p_stream::on_sam_connect"); + m_state = sam_idle; + + if (ec) + { + h(ec); + return; + } + + do_name_lookup("ME", wrap_allocator( + [this](error_code const& e, char const* dst, Handler hn) { + set_local_endpoint(e, dst, std::move(hn)); + }, std::move(h))); + } + + using name_lookup_handler = std::function; + + template + void do_name_lookup(std::string const& name, 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(wrap_allocator( + [this,s=m_sam_socket](error_code const& ec, Handler hn) { + on_name_lookup(ec, s, std::move(hn)); + }, std::move(handler))); + } + + template + void on_name_lookup(error_code const& ec, std::shared_ptr, Handler handler) + { + 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()); + } + + + template + void set_local_endpoint(error_code const& ec, char const* dest, Handler h) + { + if (!ec && dest != nullptr) + m_i2p_local_endpoint = dest; + else + m_i2p_local_endpoint.clear(); + + h(ec); + } + + // 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_context& m_io_service; +}; + +} + +#endif // TORRENT_USE_I2P + +#endif diff --git a/docs/include/libtorrent/identify_client.hpp b/docs/include/libtorrent/identify_client.hpp new file mode 100644 index 0000000..64bf9b2 --- /dev/null +++ b/docs/include/libtorrent/identify_client.hpp @@ -0,0 +1,86 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2003, 2006, 2013-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_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 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // 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); + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif // TORRENT_ABI_VERSION + +} + +#endif // TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED diff --git a/docs/include/libtorrent/index_range.hpp b/docs/include/libtorrent/index_range.hpp new file mode 100644 index 0000000..ad5e292 --- /dev/null +++ b/docs/include/libtorrent/index_range.hpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2018-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_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() const { return index_iter{_begin}; } + index_iter end() const { return index_iter{_end}; } +}; + +} + +#endif diff --git a/docs/include/libtorrent/info_hash.hpp b/docs/include/libtorrent/info_hash.hpp new file mode 100644 index 0000000..c93f53f --- /dev/null +++ b/docs/include/libtorrent/info_hash.hpp @@ -0,0 +1,166 @@ +/* + +Copyright (c) 2018, BitTorrent Inc. +Copyright (c) 2018, Steven Siloti +Copyright (c) 2019-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_INFO_HASH_HPP_INCLUDED +#define TORRENT_INFO_HASH_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/sha1_hash.hpp" + +#if TORRENT_USE_IOSTREAM +#include +#endif // TORRENT_USE_IOSTREAM + +namespace libtorrent +{ + // BitTorrent version enumerator + enum class protocol_version : std::uint8_t + { + // The original BitTorrent version, using SHA-1 hashes + V1, + // Version 2 of the BitTorrent protocol, using SHA-256 hashes + V2, + NUM + }; + + // internal + constexpr std::size_t num_protocols = int(protocol_version::NUM); + +namespace { + std::initializer_list const all_versions{ + protocol_version::V1, + protocol_version::V2 + }; +} + + // class holding the info-hash of a torrent. It can hold a v1 info-hash + // (SHA-1) or a v2 info-hash (SHA-256) or both. + // + // .. note:: + // + // If ``has_v2()`` is false then the v1 hash might actually be a truncated + // v2 hash + struct TORRENT_EXPORT info_hash_t + { + // The default constructor creates an object that has neither a v1 or v2 + // hash. + // + // For backwards compatibility, make it possible to construct directly + // from a v1 hash. This constructor allows *implicit* conversion from a + // v1 hash, but the implicitness is deprecated. + info_hash_t() noexcept = default; + explicit info_hash_t(sha1_hash h1) noexcept : v1(h1) {} // NOLINT + explicit info_hash_t(sha256_hash h2) noexcept : v2(h2) {} + info_hash_t(sha1_hash h1, sha256_hash h2) noexcept + : v1(h1), v2(h2) {} + + // hidden + info_hash_t(info_hash_t const&) noexcept = default; + info_hash_t& operator=(info_hash_t const&) & noexcept = default; + + // returns true if the corresponding info hash is present in this + // object. + bool has_v1() const { return !v1.is_all_zeros(); } + bool has_v2() const { return !v2.is_all_zeros(); } + bool has(protocol_version v) const + { + TORRENT_ASSERT(v != protocol_version::NUM); + return v == protocol_version::V1 ? has_v1() : has_v2(); + } + + // returns the has for the specified protocol version + sha1_hash get(protocol_version v) const + { + TORRENT_ASSERT(v != protocol_version::NUM); + return v == protocol_version::V1 ? v1 : sha1_hash(v2.data()); + } + + // returns the v2 (truncated) info-hash, if there is one, otherwise + // returns the v1 info-hash + sha1_hash get_best() const + { + return has_v2() ? get(protocol_version::V2) : v1; + } + + friend bool operator!=(info_hash_t const& lhs, info_hash_t const& rhs) + { + return std::tie(lhs.v1, lhs.v2) != std::tie(rhs.v1, rhs.v2); + } + + friend bool operator==(info_hash_t const& lhs, info_hash_t const& rhs) noexcept + { + return std::tie(lhs.v1, lhs.v2) == std::tie(rhs.v1, rhs.v2); + } + + // calls the function object ``f`` for each hash that is available. + // starting with v1. The signature of ``F`` is:: + // + // void(sha1_hash, protocol_version); + template void for_each(F f) const + { + if (has_v1()) f(v1, protocol_version::V1); + if (has_v2()) f(sha1_hash(v2.data()), protocol_version::V2); + } + + bool operator<(info_hash_t const& o) const + { + return std::tie(v1, v2) < std::tie(o.v1, o.v2); + } + +#if TORRENT_USE_IOSTREAM + friend std::ostream& operator<<(std::ostream& os, info_hash_t const& ih) + { + return os << '[' << ih.v1 << ',' << ih.v2 << ']'; + } +#endif // TORRENT_USE_IOSTREAM + + sha1_hash v1; + sha256_hash v2; + }; + +} + +namespace std { + template <> + struct hash + { + std::size_t operator()(libtorrent::info_hash_t const& k) const + { + return std::hash{}(k.v1) + ^ std::hash{}(k.v2) ; + } + }; +} + +#endif diff --git a/docs/include/libtorrent/io.hpp b/docs/include/libtorrent/io.hpp new file mode 100644 index 0000000..29b3676 --- /dev/null +++ b/docs/include/libtorrent/io.hpp @@ -0,0 +1,189 @@ +/* + +Copyright (c) 2004, 2007, 2009, 2011, 2016-2017, 2019, Arvid Norberg +Copyright (c) 2016-2017, 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_IO_HPP_INCLUDED +#define TORRENT_IO_HPP_INCLUDED + +#include +#include +#include // for copy +#include // for memcpy +#include +#include + +#include "assert.hpp" +#include "libtorrent/aux_/io.hpp" + +namespace libtorrent { +namespace aux { + + // 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 aux +} + +#endif // TORRENT_IO_HPP_INCLUDED diff --git a/docs/include/libtorrent/io_context.hpp b/docs/include/libtorrent/io_context.hpp new file mode 100644 index 0000000..efb2710 --- /dev/null +++ b/docs/include/libtorrent/io_context.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2006-2007, 2009, 2015, 2017, 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_IO_CONTEXT_HPP_INCLUDED +#define TORRENT_IO_CONTEXT_HPP_INCLUDED + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#else +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // SIMULATOR + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::io_context; +#else + using boost::asio::io_context; +#endif + using boost::asio::executor_work_guard; + using boost::asio::make_work_guard; + + using boost::asio::post; + using boost::asio::dispatch; + using boost::asio::defer; +} + +#endif diff --git a/docs/include/libtorrent/io_service.hpp b/docs/include/libtorrent/io_service.hpp new file mode 100644 index 0000000..86b0b99 --- /dev/null +++ b/docs/include/libtorrent/io_service.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2006-2007, 2009, 2015, 2017, 2019, Arvid Norberg +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_IO_SERVICE_HPP_INCLUDED +#define TORRENT_IO_SERVICE_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#error warning "this header is deprecated, use io_context.hpp instead" +namespace libtorrent { + + using io_service = boost::asio::io_context; +} + +#endif diff --git a/docs/include/libtorrent/ip_filter.hpp b/docs/include/libtorrent/ip_filter.hpp new file mode 100644 index 0000000..fdaccc0 --- /dev/null +++ b/docs/include/libtorrent/ip_filter.hpp @@ -0,0 +1,241 @@ +/* + +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2005-2007, 2009-2010, 2013, 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016-2018, 2020, 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_IP_FILTER_HPP +#define TORRENT_IP_FILTER_HPP + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include +#include + +#include "libtorrent/address.hpp" + +namespace libtorrent { + +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 aux { + + template + TORRENT_EXTRA_EXPORT Addr zero(); + template + TORRENT_EXTRA_EXPORT Addr plus_one(Addr const& a); + template + TORRENT_EXTRA_EXPORT Addr minus_one(Addr const& a); + template + TORRENT_EXTRA_EXPORT Addr max_addr(); + + extern template address_v4::bytes_type minus_one(address_v4::bytes_type const&); + extern template address_v6::bytes_type minus_one(address_v6::bytes_type const&); + extern template address_v4::bytes_type plus_one(address_v4::bytes_type const&); + extern template address_v6::bytes_type plus_one(address_v6::bytes_type const&); + extern template address_v4::bytes_type zero(); + extern template address_v6::bytes_type zero(); + extern template address_v4::bytes_type max_addr(); + extern template address_v6::bytes_type max_addr(); + + inline std::uint16_t plus_one(std::uint16_t val) { return val + 1; } + inline std::uint16_t minus_one(std::uint16_t val) { return val - 1; } + template<> + inline std::uint16_t zero() { return 0; } + 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(); + bool empty() const; + void add_rule(Addr first, Addr last, std::uint32_t flags); + std::uint32_t access(Addr const& addr) const; + template + std::vector> export_filter() const; + + 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; + friend bool operator==(range const& lhs, range const& rhs) + { return lhs.start == rhs.start && lhs.access == rhs.access; } + }; + + std::set m_access_list; + }; + + extern template class filter_impl; + extern template class filter_impl; + extern template class filter_impl; + + extern template std::vector> filter_impl::export_filter() const; + extern template std::vector> filter_impl::export_filter() const; + extern template std::vector> filter_impl::export_filter() const; +} + +// 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 +{ + ip_filter(); + ip_filter(ip_filter const&); + ip_filter(ip_filter&&); + ip_filter& operator=(ip_filter const&); + ip_filter& operator=(ip_filter&&); + ~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 + }; + + // returns true if the filter does not contain any rules + bool empty() const; + + // 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; + +private: + + aux::filter_impl m_filter4; + aux::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: + + port_filter(); + port_filter(port_filter const&); + port_filter(port_filter&&); + port_filter& operator=(port_filter const&); + port_filter& operator=(port_filter&&); + ~port_filter(); + + // 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: + + aux::filter_impl m_filter; + +}; + +} + +#endif diff --git a/docs/include/libtorrent/ip_voter.hpp b/docs/include/libtorrent/ip_voter.hpp new file mode 100644 index 0000000..e0e2846 --- /dev/null +++ b/docs/include/libtorrent/ip_voter.hpp @@ -0,0 +1,136 @@ +/* + +Copyright (c) 2013-2015, 2017, 2019, Arvid Norberg +Copyright (c) 2016, Steven Siloti +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_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/docs/include/libtorrent/libtorrent.hpp b/docs/include/libtorrent/libtorrent.hpp new file mode 100644 index 0000000..756ffb2 --- /dev/null +++ b/docs/include/libtorrent/libtorrent.hpp @@ -0,0 +1,168 @@ + +// This header is generated by tools/gen_convenience_header.py + +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/bloom_filter.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/choker.hpp" +#include "libtorrent/client_data.hpp" +#include "libtorrent/close_reason.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/copy_ptr.hpp" +#include "libtorrent/crc32c.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/disabled_disk_io.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/disk_observer.hpp" +#include "libtorrent/download_priority.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/extensions/smart_ban.hpp" +#include "libtorrent/extensions/ut_metadata.hpp" +#include "libtorrent/extensions/ut_pex.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/gzip.hpp" +#include "libtorrent/hash_picker.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/hex.hpp" +#include "libtorrent/http_connection.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/http_seed_connection.hpp" +#include "libtorrent/http_stream.hpp" +#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/i2p_stream.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/index_range.hpp" +#include "libtorrent/info_hash.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/kademlia/announce_flags.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/kademlia/dht_settings.hpp" +#include "libtorrent/kademlia/dht_state.hpp" +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/kademlia/direct_request.hpp" +#include "libtorrent/kademlia/dos_blocker.hpp" +#include "libtorrent/kademlia/ed25519.hpp" +#include "libtorrent/kademlia/find_data.hpp" +#include "libtorrent/kademlia/get_item.hpp" +#include "libtorrent/kademlia/get_peers.hpp" +#include "libtorrent/kademlia/io.hpp" +#include "libtorrent/kademlia/item.hpp" +#include "libtorrent/kademlia/msg.hpp" +#include "libtorrent/kademlia/node.hpp" +#include "libtorrent/kademlia/node_entry.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/observer.hpp" +#include "libtorrent/kademlia/put_data.hpp" +#include "libtorrent/kademlia/refresh.hpp" +#include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/kademlia/rpc_manager.hpp" +#include "libtorrent/kademlia/sample_infohashes.hpp" +#include "libtorrent/kademlia/traversal_algorithm.hpp" +#include "libtorrent/kademlia/types.hpp" +#include "libtorrent/link.hpp" +#include "libtorrent/lsd.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/mmap_disk_io.hpp" +#include "libtorrent/mmap_storage.hpp" +#include "libtorrent/natpmp.hpp" +#include "libtorrent/netlink.hpp" +#include "libtorrent/operations.hpp" +#include "libtorrent/optional.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/part_file.hpp" +#include "libtorrent/pe_crypto.hpp" +#include "libtorrent/peer.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/peer_class_type_filter.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/peer_connection_handle.hpp" +#include "libtorrent/peer_connection_interface.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/peer_list.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/pex_flags.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/platform_util.hpp" +#include "libtorrent/portmap.hpp" +#include "libtorrent/posix_disk_io.hpp" +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/puff.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/read_resume_data.hpp" +#include "libtorrent/request_blocks.hpp" +#include "libtorrent/resolve_links.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/session_handle.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/session_types.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/sha1.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/sha256.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/socks5_stream.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/ssl.hpp" +#include "libtorrent/ssl_stream.hpp" +#include "libtorrent/stack_allocator.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/tailqueue.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/torrent_flags.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/udp_tracker_connection.hpp" +#include "libtorrent/union_endpoint.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/upnp.hpp" +#include "libtorrent/utf8.hpp" +#include "libtorrent/vector_utils.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/web_connection_base.hpp" +#include "libtorrent/web_peer_connection.hpp" +#include "libtorrent/write_resume_data.hpp" +#include "libtorrent/xml_parse.hpp" diff --git a/docs/include/libtorrent/link.hpp b/docs/include/libtorrent/link.hpp new file mode 100644 index 0000000..14d8dce --- /dev/null +++ b/docs/include/libtorrent/link.hpp @@ -0,0 +1,83 @@ +/* + +Copyright (c) 2010, 2013-2019, Arvid Norberg +Copyright (c) 2017, 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_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/docs/include/libtorrent/lsd.hpp b/docs/include/libtorrent/lsd.hpp new file mode 100644 index 0000000..353b368 --- /dev/null +++ b/docs/include/libtorrent/lsd.hpp @@ -0,0 +1,95 @@ +/* + +Copyright (c) 2007, 2009, 2012, 2014-2015, 2017, 2019-2020, Arvid Norberg +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_LSD_HPP +#define TORRENT_LSD_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/aux_/lsd.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent { + +struct lsd : std::enable_shared_from_this +{ + lsd(io_context& ios, aux::lsd_callback& cb + , address listen_address, address 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& info_hash + , 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/docs/include/libtorrent/magnet_uri.hpp b/docs/include/libtorrent/magnet_uri.hpp new file mode 100644 index 0000000..62a0a29 --- /dev/null +++ b/docs/include/libtorrent/magnet_uri.hpp @@ -0,0 +1,88 @@ +/* + +Copyright (c) 2007-2009, 2012-2013, 2016-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_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; + struct 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 + , 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/docs/include/libtorrent/mmap_disk_io.hpp b/docs/include/libtorrent/mmap_disk_io.hpp new file mode 100644 index 0000000..b58ba53 --- /dev/null +++ b/docs/include/libtorrent/mmap_disk_io.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2007-2018, Steven Siloti +Copyright (c) 2007, 2013-2016, 2019-2020, Arvid Norberg +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_DISK_IO_THREAD +#define TORRENT_DISK_IO_THREAD + +#include "libtorrent/config.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/io_context.hpp" + +namespace libtorrent { + +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + + struct counters; + struct settings_interface; + + // constructs a memory mapped file disk I/O object. + TORRENT_EXPORT std::unique_ptr mmap_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); + +#endif // HAVE_MMAP || HAVE_MAP_VIEW_OF_FILE + +} + +#endif // TORRENT_DISK_IO_THREAD diff --git a/docs/include/libtorrent/mmap_storage.hpp b/docs/include/libtorrent/mmap_storage.hpp new file mode 100644 index 0000000..1bfb330 --- /dev/null +++ b/docs/include/libtorrent/mmap_storage.hpp @@ -0,0 +1,227 @@ +/* + +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2003, 2009, 2011, 2013-2020, Arvid Norberg +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2018-2019, 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_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/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" +#include "libtorrent/aux_/open_mode.hpp" // for aux::open_mode_t +#include "libtorrent/disk_interface.hpp" // for disk_job_flags_t + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + +namespace aux { + struct session_settings; + struct file_view_pool; + struct file_view; +} + + struct TORRENT_EXTRA_EXPORT mmap_storage + : std::enable_shared_from_this + , aux::disk_job_fence + { + // constructs the mmap_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_view_pool`` is the cache of file mappings that the storage will use. + // All files it opens will ask the file_view_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. + mmap_storage(storage_params const& params, aux::file_view_pool&); + + // hidden + ~mmap_storage(); + mmap_storage(mmap_storage const&) = delete; + mmap_storage& operator=(mmap_storage const&) = delete; + + void abort_jobs(); + + bool has_any_file(storage_error&); + void set_file_priority(settings_interface const& + , aux::vector& prio + , storage_error&); + void rename_file(file_index_t index, std::string const& new_filename + , storage_error&); + void release_files(storage_error&); + void delete_files(remove_flags_t options, storage_error&); + status_t initialize(settings_interface const&, storage_error&); + std::pair move_storage(std::string save_path + , move_flags_t, storage_error&); + bool verify_resume_data(add_torrent_params const& rd + , aux::vector const& links + , storage_error&); + bool tick(); + + int readv(settings_interface const&, span bufs + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags + , storage_error&); + int writev(settings_interface const&, span bufs + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags + , storage_error&); + int hashv(settings_interface const&, hasher& ph, std::ptrdiff_t len + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags, storage_error&); + int hashv2(settings_interface const&, hasher256& ph, std::ptrdiff_t len + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags, storage_error&); + + // 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 : m_files; } + file_storage const& orig_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; } + + storage_index_t storage_index() const { return m_storage_index; } + void set_storage_index(storage_index_t st) { m_storage_index = st; } + + 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 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}; + + 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 + boost::optional open_file(settings_interface const&, file_index_t + , aux::open_mode_t, storage_error&) const; + boost::optional open_file_impl(settings_interface const& + , file_index_t, aux::open_mode_t, storage_error&) 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 + aux::file_view_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; + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + // Windows has a race condition when unmapping a view while a new + // view or mapping object is being created in a different thread. + // The race can cause a page of written data to be zeroed out before + // it is written out to disk. To avoid the race these calls must be + // serialized on a per-file basis. See github issue #3842 for details. + + // This array stores a mutex for each file in the storage object + // It must be aquired before calling CreateFileMapping or UnmapViewOfFile + mutable std::shared_ptr m_file_open_unmap_lock; +#endif + + bool m_allocate_files; + }; + +} + +#endif // TORRENT_STORAGE_HPP_INCLUDED diff --git a/docs/include/libtorrent/natpmp.hpp b/docs/include/libtorrent/natpmp.hpp new file mode 100644 index 0000000..cb0a523 --- /dev/null +++ b/docs/include/libtorrent/natpmp.hpp @@ -0,0 +1,219 @@ +/* + +Copyright (c) 2007-2010, 2015-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2018, 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_NATPMP_HPP +#define TORRENT_NATPMP_HPP + +#include "libtorrent/io_context.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 +#include "libtorrent/aux_/listen_socket_handle.hpp" + +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); +} // namespace errors + + TORRENT_EXPORT boost::system::error_category& pcp_category(); +} // namespace libtorrent + +namespace boost { +namespace system { + template<> struct is_error_code_enum + { static const bool value = true; }; +} +} + +namespace libtorrent { + +struct TORRENT_EXTRA_EXPORT natpmp final + : std::enable_shared_from_this + , single_threaded +{ + natpmp(io_context& ios, aux::portmap_callback& cb, aux::listen_socket_handle ls); + + 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}; + + io_context& m_ioc; + + aux::listen_socket_handle m_listen_handle; + + bool m_disabled = false; + + bool m_abort = false; +}; + +} // namespace libtorrent + +#endif diff --git a/docs/include/libtorrent/netlink.hpp b/docs/include/libtorrent/netlink.hpp new file mode 100644 index 0000000..0f15360 --- /dev/null +++ b/docs/include/libtorrent/netlink.hpp @@ -0,0 +1,203 @@ +/* + +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, 2019, Arvid Norberg +Copyright (c) 2018, Eugene Shalygin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/docs/include/libtorrent/operations.hpp b/docs/include/libtorrent/operations.hpp new file mode 100644 index 0000000..f46f377 --- /dev/null +++ b/docs/include/libtorrent/operations.hpp @@ -0,0 +1,265 @@ +/* + +Copyright (c) 2015, 2017-2020, Arvid Norberg +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_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 of network routes + enum_route, + + // moving read/write position in a file, operation_t::hostname_lookup + file_seek, + + // an async wait operation on a timer + timer, + + // call to mmap() (or windows counterpart) + file_mmap, + + // call to ftruncate() (or SetEndOfFile() on windows) + file_truncate, + }; + + // 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/docs/include/libtorrent/optional.hpp b/docs/include/libtorrent/optional.hpp new file mode 100644 index 0000000..bf85715 --- /dev/null +++ b/docs/include/libtorrent/optional.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2017, 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_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/docs/include/libtorrent/parse_url.hpp b/docs/include/libtorrent/parse_url.hpp new file mode 100644 index 0000000..5123cdb --- /dev/null +++ b/docs/include/libtorrent/parse_url.hpp @@ -0,0 +1,66 @@ +/* + +Copyright (c) 2008-2009, 2014, 2016-2017, 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_PARSE_URL_HPP_INCLUDED +#define TORRENT_PARSE_URL_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include +#include + +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_view.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); + + // returns true if the hostname contains any IDNA (internationalized domain + // name) labels. + TORRENT_EXTRA_EXPORT bool is_idna(string_view hostname); + + // the query string is the part of the URL immediately following "?", i.e. + // the query string arguments. This function returns true if any of the + // arguments are "info_hash", "port", "key", "event", "uploaded", + // "downloaded", "left" or "corrupt". + TORRENT_EXTRA_EXPORT bool has_tracker_query_string(string_view query_string); +} + +#endif diff --git a/docs/include/libtorrent/part_file.hpp b/docs/include/libtorrent/part_file.hpp new file mode 100644 index 0000000..adbd736 --- /dev/null +++ b/docs/include/libtorrent/part_file.hpp @@ -0,0 +1,138 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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" +#include "libtorrent/hasher.hpp" +#include "libtorrent/aux_/open_mode.hpp" +#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t + +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 path, std::string 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); + int hashv(hasher& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec); + int hashv2(hasher256& ph, std::ptrdiff_t len, 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(aux::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; + } + + template + int do_hashv(Hasher& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec); + + 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/docs/include/libtorrent/pe_crypto.hpp b/docs/include/libtorrent/pe_crypto.hpp new file mode 100644 index 0000000..3c2280e --- /dev/null +++ b/docs/include/libtorrent/pe_crypto.hpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2007, Un Shyam +Copyright (c) 2007-2009, 2011-2012, 2014-2019, Arvid Norberg +Copyright (c) 2016-2017, 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_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/aux_/receive_buffer.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/span.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(); + + // 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(aux::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 + , aux::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/docs/include/libtorrent/peer.hpp b/docs/include/libtorrent/peer.hpp new file mode 100644 index 0000000..9d3b0e3 --- /dev/null +++ b/docs/include/libtorrent/peer.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2003-2004, 2006, 2012, 2014-2017, 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_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/docs/include/libtorrent/peer_class.hpp b/docs/include/libtorrent/peer_class.hpp new file mode 100644 index 0000000..8d6f0a9 --- /dev/null +++ b/docs/include/libtorrent/peer_class.hpp @@ -0,0 +1,159 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016, 2020, 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_PEER_CLASS_HPP_INCLUDED +#define TORRENT_PEER_CLASS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/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 + aux::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/docs/include/libtorrent/peer_class_set.hpp b/docs/include/libtorrent/peer_class_set.hpp new file mode 100644 index 0000000..0e2646c --- /dev/null +++ b/docs/include/libtorrent/peer_class_set.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2010, 2013-2015, 2019, Arvid Norberg +Copyright (c) 2016-2017, 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_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/docs/include/libtorrent/peer_class_type_filter.hpp b/docs/include/libtorrent/peer_class_type_filter.hpp new file mode 100644 index 0000000..bc6815b --- /dev/null +++ b/docs/include/libtorrent/peer_class_type_filter.hpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2014, 2016-2017, 2019, Arvid Norberg +Copyright (c) 2017, 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_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/docs/include/libtorrent/peer_connection.hpp b/docs/include/libtorrent/peer_connection.hpp new file mode 100644 index 0000000..0a00af1 --- /dev/null +++ b/docs/include/libtorrent/peer_connection.hpp @@ -0,0 +1,1255 @@ +/* + +Copyright (c) 2016, Falcosc +Copyright (c) 2003-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/peer_id.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/aux_/bandwidth_limit.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/chained_buffer.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/aux_/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_context.hpp" +#include "libtorrent/aux_/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 "libtorrent/aux_/socket_type.hpp" + +#include +#include +#include +#include +#include // for std::forward +#include // for make_tuple +#include +#include + +namespace libtorrent { + + struct torrent; + struct torrent_peer; + struct disk_interface; + +#ifndef TORRENT_DISABLE_EXTENSIONS + struct peer_plugin; +#endif + +namespace aux { + + struct session_interface; + + struct min_value_t {}; + static const min_value_t min_value{}; + + struct relative_time + { + relative_time() : m_time_diff(0) {} + explicit relative_time(min_value_t) : m_time_diff(std::numeric_limits::min()) {} + void set(time_point const reference, time_point const new_value) noexcept + { + m_time_diff = duration_cast(new_value - reference); + } + + time_point get(time_point reference) const noexcept + { + return reference + m_time_diff; + } + private: + milliseconds32 m_time_diff; + }; + + template + T clamp_assign(int const v) + { + auto const limit = std::numeric_limits::max(); + if (v < 0) return 0; + if (v > int(limit)) return limit; + return static_cast(v); + } +} + + 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_context* ios; + std::weak_ptr tor; + aux::socket_type 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; + + struct TORRENT_EXTRA_EXPORT peer_connection + : peer_connection_hot_members + , aux::bandwidth_socket + , peer_class_set + , disk_observer + , peer_connection_interface + , std::enable_shared_from_this + { + friend struct invariant_access; + friend struct torrent; + friend struct cork; + + // explicitly disallow assignment, to silence msvc warning + peer_connection& operator=(peer_connection const&) = delete; + + void on_exception(std::exception const& e); + void on_error(error_code const& ec); + + virtual connection_type type() const = 0; + + enum channels + { + upload_channel, + download_channel, + num_channels + }; + + explicit peer_connection(peer_connection_args& 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 int(m_prefer_contiguous_blocks); + } + + bool on_parole() const; + + picker_options_t picker_options() const; + + void prefer_contiguous_blocks(int const num) + { + m_prefer_contiguous_blocks = aux::clamp_assign(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); + bool share_mode() const { return m_share_mode; } +#endif + + void set_upload_only(bool); + bool upload_only() const { return m_upload_only || is_seed() || m_have_all; } + + 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; } + + // get the info hash associated with this peer + // this will be a sha1 hash or truncated sha256 hash depending + // on which protocol version this connection is using + sha1_hash associated_info_hash() const; + + 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); + + aux::socket_type const& get_socket() const { return m_socket; } + aux::socket_type& get_socket() { 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.get(m_connect); } + + // 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(); + +#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; + + 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.get(m_connect); } + + // 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(info_hash_t const& ih); + + bool validate_piece_request(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_context& get_context() { 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); + + void do_update_interest(); + void fill_send_buffer(); + void on_disk_read_complete(disk_buffer_holder buffer + , storage_error const& error, peer_request const&, time_point issue_time); + void on_disk_write_complete(storage_error const& error + , peer_request const&, std::shared_ptr); + void on_seed_mode_hashed(piece_index_t piece + , sha1_hash const& piece_hash, aux::vector const& block_hashes + , storage_error const& error); + + // this is for a future per-block request feature +#if 0 + void on_hash2_complete(storage_error const& error, peer_request const& r + , sha256_hash const& hash); +#endif + int request_timeout() const; + void check_graceful_pause(); + + int wanted_transfer(int channel); + int request_bandwidth(int channel, int bytes = 0); + + aux::socket_type 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: + aux::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. + std::uint16_t 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: + aux::chained_buffer m_send_buffer; + private: + + // the disk thread to use to issue disk jobs to + disk_interface& m_disk_thread; + + // io service + io_context& 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_context running as long as we + // have peer connections + executor_work_guard m_work; + + // the time when we last got a part of a + // piece packet from this peer + aux::relative_time m_last_piece; + + // the time we sent a request to + // this peer the last time + aux::relative_time m_last_request; + + // the time we received the last + // piece request from the peer + aux::relative_time m_last_incoming_request{aux::min_value}; + + // the time when we unchoked this peer + aux::relative_time m_last_unchoke; + + // if we're unchoked by this peer, this + // was the time + aux::relative_time m_last_unchoked; + + // the time we last choked this peer. min_time() in + // case we never unchoked it + aux::relative_time m_last_choke{aux::min_value}; + + // timeouts + aux::relative_time m_last_receive; + aux::relative_time m_last_sent; + + // the last time we filled our send buffer with payload + // this is used for timeouts + aux::relative_time m_last_sent_payload; + + // 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/ + aux::relative_time m_requested; + + // the time when this peer sent us a not_interested message + // the last time. + aux::relative_time m_became_uninterested; + + // the time when we sent a not_interested message to + // this peer the last time. + aux::relative_time m_became_uninteresting; + + // the time when async_connect was called + // or when the incoming connection was established + time_point m_connect = 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 + std::uint16_t 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. + std::uint16_t 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; + + // 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. + std::uint16_t 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 has told us explicitly that it is only + // uploading. A seed is *implicitly* upload only, so this is not + // necessarily true. + 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/docs/include/libtorrent/peer_connection_handle.hpp b/docs/include/libtorrent/peer_connection_handle.hpp new file mode 100644 index 0000000..f6805f5 --- /dev/null +++ b/docs/include/libtorrent/peer_connection_handle.hpp @@ -0,0 +1,158 @@ +/* + +Copyright (c) 2015, 2017, Steven Siloti +Copyright (c) 2016-2019, Arvid Norberg +Copyright (c) 2016-2018, 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_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 { + +struct 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/docs/include/libtorrent/peer_connection_interface.hpp b/docs/include/libtorrent/peer_connection_interface.hpp new file mode 100644 index 0000000..489afb3 --- /dev/null +++ b/docs/include/libtorrent/peer_connection_interface.hpp @@ -0,0 +1,84 @@ +/* + +Copyright (c) 2014-2019, Arvid Norberg +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_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/docs/include/libtorrent/peer_id.hpp b/docs/include/libtorrent/peer_id.hpp new file mode 100644 index 0000000..0f3a480 --- /dev/null +++ b/docs/include/libtorrent/peer_id.hpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2003, 2009, 2013, 2016-2017, 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_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/docs/include/libtorrent/peer_info.hpp b/docs/include/libtorrent/peer_info.hpp new file mode 100644 index 0000000..98920f4 --- /dev/null +++ b/docs/include/libtorrent/peer_info.hpp @@ -0,0 +1,470 @@ +/* + +Copyright (c) 2003-2011, 2013-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016, Alden Torres +Copyright (c) 2019, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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" +#include "libtorrent/aux_/deprecated.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; + + using connection_type_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 human readable 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. This is UTF-8 + // encoded. + 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 outgoing_connection = 5_bit; + + // deprecated synonym for outgoing_connection + 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 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; + +#if TORRENT_ABI_VERSION <= 2 + using connection_type_t = libtorrent::connection_type_t; +#endif + // Regular bittorrent connection + static constexpr connection_type_t standard_bittorrent = 0_bit; + + // HTTP connection using the `BEP 19`_ protocol + static constexpr connection_type_t web_seed = 1_bit; + + // HTTP connection using the `BEP 17`_ protocol + static constexpr connection_type_t http_seed = 2_bit; + + // the kind of connection this peer uses. See connection_type_t. + connection_type_t connection_type; + +#if TORRENT_ABI_VERSION == 1 + // an estimate of the rate this peer is downloading at, in + // bytes per second. + TORRENT_DEPRECATED 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 int 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 static constexpr bandwidth_state_flags_t bw_torrent = bw_limit; + TORRENT_DEPRECATED 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 int upload_limit; + TORRENT_DEPRECATED 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 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/docs/include/libtorrent/peer_list.hpp b/docs/include/libtorrent/peer_list.hpp new file mode 100644 index 0000000..9305126 --- /dev/null +++ b/docs/include/libtorrent/peer_list.hpp @@ -0,0 +1,270 @@ +/* + +Copyright (c) 2003-2005, 2007-2009, 2011-2012, 2014-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2009, Daniel Wallin +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016-2017, Alden Torres +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_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/aux_/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_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, pex_flags_t, torrent_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); + + 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/docs/include/libtorrent/peer_request.hpp b/docs/include/libtorrent/peer_request.hpp new file mode 100644 index 0000000..a96970b --- /dev/null +++ b/docs/include/libtorrent/peer_request.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2004, 2009, 2013-2014, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2004, 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_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 byte 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/docs/include/libtorrent/performance_counters.hpp b/docs/include/libtorrent/performance_counters.hpp new file mode 100644 index 0000000..db79c0e --- /dev/null +++ b/docs/include/libtorrent/performance_counters.hpp @@ -0,0 +1,500 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2019, 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_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_EXPORT counters + { + // internal + 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, + + // bittorrent message counters + // 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_outgoing_hash_request, + num_outgoing_hashes, + num_outgoing_hash_reject, + + num_piece_passed, + num_piece_failed, + + num_have_pieces, + num_total_pieces_added, + + num_blocks_written, + num_blocks_read, + num_blocks_hashed, + 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 + + // internal + 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 this + // mode, blocks are allowed to be requested from more than one peer at + // at time. + num_peers_end_game, + request_latency, + + 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_file_priority, + num_fenced_load_torrent, + num_fenced_clear_piece, + num_fenced_tick_storage, + + 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 isn'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/docs/include/libtorrent/pex_flags.hpp b/docs/include/libtorrent/pex_flags.hpp new file mode 100644 index 0000000..6310f3b --- /dev/null +++ b/docs/include/libtorrent/pex_flags.hpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2018, 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_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 = 0_bit; + + // the peer is a seed + constexpr pex_flags_t pex_seed = 1_bit; + + // the peer supports the uTP, transport protocol over UDP. + constexpr pex_flags_t pex_utp = 2_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 = 3_bit; + + // protocol v2 + // this is not a standard flag, it is only used internally + constexpr pex_flags_t pex_lt_v2 = 7_bit; +} + +#endif + diff --git a/docs/include/libtorrent/piece_block.hpp b/docs/include/libtorrent/piece_block.hpp new file mode 100644 index 0000000..91091c2 --- /dev/null +++ b/docs/include/libtorrent/piece_block.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, 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_PIECE_BLOCK_HPP_INCLUDED +#define TORRENT_PIECE_BLOCK_HPP_INCLUDED + +#include "libtorrent/units.hpp" + +namespace libtorrent { + + struct TORRENT_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/docs/include/libtorrent/piece_block_progress.hpp b/docs/include/libtorrent/piece_block_progress.hpp new file mode 100644 index 0000000..16c6a53 --- /dev/null +++ b/docs/include/libtorrent/piece_block_progress.hpp @@ -0,0 +1,61 @@ +/* + +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2005, 2014, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016-2017, 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_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/docs/include/libtorrent/piece_picker.hpp b/docs/include/libtorrent/piece_picker.hpp new file mode 100644 index 0000000..81252da --- /dev/null +++ b/docs/include/libtorrent/piece_picker.hpp @@ -0,0 +1,906 @@ +/* + +Copyright (c) 2003-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2019, 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_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" +#include "libtorrent/index_range.hpp" + +namespace libtorrent { + + struct torrent; + struct 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 bytes, out of those pieces, that are pad + // files + int pad_bytes; + // true if the last piece is part of the set + bool last_piece; + }; + + struct 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; + + // 5_bit is available + + // 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) + , hashing(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 blocks_per_piece. + // The 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; + +#if 1 + // set to 1 if there is an outstanding hash request for this piece + std::uint16_t hashing:1; + + // this is for a future per-block request feature +#else + // available for future use + std::uint16_t unused:1; + + // number of outstanding hash jobs for this piece + std::uint16_t hashing:15; + + // available for future use + std::uint16_t unused2:1; +#endif + }; + + piece_picker(std::int64_t total_size, int piece_size); + + 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, torrent_peer const*); + void dec_refcount(piece_index_t, torrent_peer const*); + + // 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); + void we_dont_have(piece_index_t); + + // 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(std::int64_t total_size, int piece_size); + int num_pieces() const { return int(m_piece_map.size()); } + + bool have_piece(piece_index_t) const; + + bool is_downloading(piece_index_t const 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, download_priority_t); + + // returns the priority for the piece at 'index' + download_priority_t piece_priority(piece_index_t) const; + + // returns the current piece priorities for all pieces + void piece_priorities(std::vector&) const; + + // This function returns a list of all blocks in pieces that this client + // has and that are interesting to download, in the + // ``interesting_blocks`` out-parameter. The blocks are returned in + // priority order. + // + // If the caller of this function decides to download a block, 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! + // + // ``pieces`` should be the bitfield of all pieces, indicating which + // pieces the client has, that can be requested from it. + // + // 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 caller. 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& ignore + , 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 started_hash_job(piece_index_t piece); + void completed_hash_job(piece_index_t piece); + + void mark_as_canceled(piece_block block, torrent_peer* peer); + void mark_as_finished(piece_block block, torrent_peer* peer); + + void set_pad_bytes(piece_index_t p, int bytes); + + // 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); + + // returns information about the given piece + void piece_info(piece_index_t, 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) const; + + // if a piece had a hash-failure, it must be restored and + // made available for redownloading + void restore_piece(piece_index_t, span blocks = {}); + + // 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) const; + + // returns true if at least one block in this piece is being hashed + // only valid for v2 torrents + bool is_hashing(piece_index_t piece) const; + + // returns true if we have the piece or if the piece + // has passed the hash check + bool has_piece_passed(piece_index_t) const; + + // returns the number of blocks there is in the given piece + int blocks_in_piece(piece_index_t) const; + + // return the peer pointers to all peers that participated in + // this piece + std::vector get_downloaders(piece_index_t) 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_bytes_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(span 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 blocks_per_piece elements in it + span blocks_for_piece(downloading_piece const& dp) const; + + private: + + // 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; + + int block_size() const + { + TORRENT_ASSERT(m_piece_size > 0); + TORRENT_ASSERT(default_block_size > 0); + return (std::min)(m_piece_size, default_block_size); + } + int blocks_per_piece() const; + int piece_size(piece_index_t p) const; + + 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_bytes() const { return m_num_pad_bytes; } + + 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; + index_range + expand_piece(piece_index_t piece, int contiguous_blocks + , 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); + 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; + + // tracks the number of bytes in a specific piece that are part of a pad + // file. The padding is assumed to be at the end of the piece, and the + // blocks covered by the pad bytes are not picked by the piece picker + 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 bytes of pad file set in this piece picker + int m_num_pad_bytes = 0; + + // the number of pad blocks that we already have + int m_have_pad_bytes = 0; + + // the number of pad blocks part of filtered pieces we don't have + int m_filtered_pad_bytes = 0; + + // the number of pad blocks we have that are also filtered + int m_have_filtered_pad_bytes = 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 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_in_last_piece = 0; + int m_piece_size = 0; + std::int64_t m_total_size = 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/docs/include/libtorrent/platform_util.hpp b/docs/include/libtorrent/platform_util.hpp new file mode 100644 index 0000000..311008e --- /dev/null +++ b/docs/include/libtorrent/platform_util.hpp @@ -0,0 +1,14 @@ +#ifndef TORRENT_PLATFORM_UTIL_HPP +#define TORRENT_PLATFORM_UTIL_HPP + +#include + +namespace libtorrent { + + int max_open_files(); + + void set_thread_name(char const* name); + +} + +#endif // TORRENT_PLATFORM_UTIL_HPP diff --git a/docs/include/libtorrent/portmap.hpp b/docs/include/libtorrent/portmap.hpp new file mode 100644 index 0000000..65a5c78 --- /dev/null +++ b/docs/include/libtorrent/portmap.hpp @@ -0,0 +1,58 @@ +/* + +Copyright (c) 2017-2019, Arvid Norberg +Copyright (c) 2018, 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_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/docs/include/libtorrent/posix_disk_io.hpp b/docs/include/libtorrent/posix_disk_io.hpp new file mode 100644 index 0000000..487ac41 --- /dev/null +++ b/docs/include/libtorrent/posix_disk_io.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2016-2017, 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_POSIX_DISK_IO +#define TORRENT_POSIX_DISK_IO + +#include "libtorrent/config.hpp" +#include "libtorrent/io_context.hpp" + +#include + +namespace libtorrent { + + struct counters; + struct disk_interface; + struct settings_interface; + + // this is a simple posix disk I/O back-end, used for systems that don't + // have a 64 bit virtual address space or don't support memory mapped files. + // It's implemented using portable C file functions and is single-threaded. + TORRENT_EXPORT std::unique_ptr posix_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); +} + +#endif + diff --git a/docs/include/libtorrent/proxy_base.hpp b/docs/include/libtorrent/proxy_base.hpp new file mode 100644 index 0000000..0c53300 --- /dev/null +++ b/docs/include/libtorrent/proxy_base.hpp @@ -0,0 +1,344 @@ +/* + +Copyright (c) 2007-2011, 2013-2015, 2017-2020, Arvid Norberg +Copyright (c) 2016, 2019, Alden Torres +Copyright (c) 2017, Jan Berkel +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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_context.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" + +namespace libtorrent { + +struct proxy_base +{ + 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_context& io_context); + ~proxy_base(); + proxy_base(proxy_base&&) noexcept = default; + proxy_base& operator=(proxy_base&&) = default; + proxy_base(proxy_base const&) = delete; + proxy_base& operator=(proxy_base const&) = delete; + + void set_proxy(std::string hostname, int port) + { + m_hostname = std::move(hostname); + m_port = port; + } + + using executor_type = tcp::socket::executor_type; + executor_type get_executor() { return m_sock.get_executor(); } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler handler) + { + m_sock.async_read_some(buffers, std::move(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 handler) + { + m_sock.async_write_some(buffers, std::move(handler)); + } + +#if BOOST_VERSION >= 106600 && !defined TORRENT_BUILD_SIMULATOR + // Compatiblity with the async_wait method introduced in boost 1.66 + + static constexpr auto wait_read = tcp::socket::wait_read; + static constexpr auto wait_write = tcp::socket::wait_write; + static constexpr auto wait_error = tcp::socket::wait_error; + + template + void async_wait(tcp::socket::wait_type type, Handler handler) + { + m_sock.async_wait(type, std::move(handler)); + } +#endif + +#ifndef BOOST_NO_EXCEPTIONS + void non_blocking(bool b) + { + m_sock.non_blocking(b); + } +#endif + + void non_blocking(bool b, error_code& ec) + { + m_sock.non_blocking(b, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const& opt) + { + m_sock.set_option(opt); + } +#endif + + template + void set_option(SettableSocketOption const& opt, error_code& ec) + { + m_sock.set_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption& opt) + { + m_sock.get_option(opt); + } +#endif + + template + void get_option(GettableSocketOption& opt, error_code& ec) + { + m_sock.get_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void bind(endpoint_type const& /* endpoint */) + { +// m_sock.bind(endpoint); + } +#endif + + void cancel() + { + m_sock.cancel(); + } + + void cancel(error_code& ec) + { + 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); + } + + 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: + + // The handler must be taken as lvalue reference here since we may not call + // it. But if we do, we want the call operator to own the function object. + template + bool handle_error(error_code const& e, Handler&& h) + { + if (!e) return false; + std::forward(h)(e); + error_code ec; + close(ec); + return true; + } + + aux::noexcept_movable m_sock; + std::string m_hostname; // proxy host + int m_port; // proxy port + + aux::noexcept_movable m_remote_endpoint; + + // TODO: 2 use the resolver interface that has a built-in cache + aux::noexcept_move_only m_resolver; +}; + +template +struct wrap_allocator_t +{ + wrap_allocator_t(Handler h, UnderlyingHandler uh) + : m_handler(std::move(h)) + , m_underlying_handler(std::move(uh)) + {} + + wrap_allocator_t(wrap_allocator_t const&) = default; + wrap_allocator_t(wrap_allocator_t&&) = default; + + template + void operator()(A&&... a) + { + m_handler(std::forward(a)..., std::move(m_underlying_handler)); + } + + using allocator_type = typename boost::asio::associated_allocator::type; + using executor_type = typename boost::asio::associated_executor::type; + + allocator_type get_allocator() const noexcept + { return boost::asio::get_associated_allocator(m_underlying_handler); } + + executor_type get_executor() const noexcept + { + return boost::asio::get_associated_executor(m_underlying_handler); + } + +private: + Handler m_handler; + UnderlyingHandler m_underlying_handler; +}; + +template +wrap_allocator_t wrap_allocator(Handler h, UnderlyingHandler u) +{ + return wrap_allocator_t{std::move(h), std::move(u)}; +} + + +} + +#endif diff --git a/docs/include/libtorrent/puff.hpp b/docs/include/libtorrent/puff.hpp new file mode 100644 index 0000000..9aa5911 --- /dev/null +++ b/docs/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/docs/include/libtorrent/random.hpp b/docs/include/libtorrent/random.hpp new file mode 100644 index 0000000..41a9dec --- /dev/null +++ b/docs/include/libtorrent/random.hpp @@ -0,0 +1,85 @@ +/* + +Copyright (c) 2011-2013, 2016-2019, Arvid Norberg +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_RANDOM_HPP_INCLUDED +#define TORRENT_RANDOM_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" + +#include +#include +#include + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT std::uint32_t random(std::uint32_t m); + +namespace aux { + + TORRENT_EXTRA_EXPORT std::mt19937& random_engine(); + + template + void random_shuffle(Range& range) + { +#ifdef TORRENT_BUILD_SIMULATOR + // in simulations, we want all shuffles to be deterministic (as long as + // the random engine is deterministic + if (range.size() == 0) return; + for (auto i = range.size() - 1; i > 0; --i) { + auto const other = random(std::uint32_t(i)); + if (i == other) continue; + using std::swap; + swap(range.data()[i], range.data()[other]); + } +#else + std::shuffle(range.data(), range.data() + range.size(), random_engine()); +#endif + } + + // Fills the buffer with pseudo 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); + + // Fills the buffer with random bytes from a strong entropy source. This can + // be used to generate secrets. + TORRENT_EXTRA_EXPORT void crypto_random_bytes(span buffer); +} +} + +#endif // TORRENT_RANDOM_HPP_INCLUDED diff --git a/docs/include/libtorrent/read_resume_data.hpp b/docs/include/libtorrent/read_resume_data.hpp new file mode 100644 index 0000000..db57f1f --- /dev/null +++ b/docs/include/libtorrent/read_resume_data.hpp @@ -0,0 +1,71 @@ +/* + +Copyright (c) 2015-2018, 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_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" +#include "libtorrent/torrent_info.hpp" // for load_torrent_limits + +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. + // + // The ``piece_limit`` parameter determines the largest number of pieces + // allowed in the torrent that may be loaded as part of the resume data, if + // it contains an ``info`` field. The overloads that take a flat buffer are + // instead configured with limits on torrent sizes via load_torrent limits. + // + // In order to support large torrents, it may also be necessary to raise the + // settings_pack::max_piece_count setting and pass a higher limit to calls + // to torrent_info::parse_info_section(). + TORRENT_EXPORT add_torrent_params read_resume_data(bdecode_node const& rd + , error_code& ec, int piece_limit = 0x200000); + TORRENT_EXPORT add_torrent_params read_resume_data(span buffer + , error_code& ec, load_torrent_limits const& cfg = {}); + TORRENT_EXPORT add_torrent_params read_resume_data(bdecode_node const& rd + , int piece_limit = 0x200000); + TORRENT_EXPORT add_torrent_params read_resume_data(span buffer + , load_torrent_limits const& cfg = {}); +} + +#endif diff --git a/docs/include/libtorrent/request_blocks.hpp b/docs/include/libtorrent/request_blocks.hpp new file mode 100644 index 0000000..fb01a8e --- /dev/null +++ b/docs/include/libtorrent/request_blocks.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2010, 2014-2015, 2017, 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_REQUEST_BLOCKS_HPP_INCLUDED +#define TORRENT_REQUEST_BLOCKS_HPP_INCLUDED + +#include "libtorrent/peer_info.hpp" + +namespace libtorrent { + + struct torrent; + struct 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/docs/include/libtorrent/resolve_links.hpp b/docs/include/libtorrent/resolve_links.hpp new file mode 100644 index 0000000..cc4439e --- /dev/null +++ b/docs/include/libtorrent/resolve_links.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +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_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" +#include "libtorrent/fwd.hpp" +#include "libtorrent/sha1_hash.hpp" // for sha256_hash + +namespace libtorrent { + +#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: + + void match_v1(std::shared_ptr const& ti + , std::string const& save_path); + void match_v2(std::shared_ptr const& ti + , std::string const& save_path); + + // 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; + + // maps file root hash to file index, in m_torrent_file + std::unordered_multimap m_file_roots; + }; +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + +} + +#endif diff --git a/docs/include/libtorrent/session.hpp b/docs/include/libtorrent/session.hpp new file mode 100644 index 0000000..38ca9a6 --- /dev/null +++ b/docs/include/libtorrent/session.hpp @@ -0,0 +1,299 @@ +/* + +Copyright (c) 2003-2004, 2006-2007, 2009-2010, 2013-2020, Arvid Norberg +Copyright (c) 2015, Steven Siloti +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_SESSION_HPP_INCLUDED +#define TORRENT_SESSION_HPP_INCLUDED + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/session_handle.hpp" +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/session_types.hpp" // for session_flags_t + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/fingerprint.hpp" +#include // for snprintf +#endif + +namespace libtorrent { + +TORRENT_VERSION_NAMESPACE_3 + struct plugin; + struct session_params; +TORRENT_VERSION_NAMESPACE_3_END + + // 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; + struct settings_interface; + + // the constructor function for the default storage. On systems that support + // memory mapped files (and a 64 bit address space) the memory mapped storage + // will be constructed, otherwise the portable posix storage. + TORRENT_EXPORT std::unique_ptr default_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); + + // 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. + struct TORRENT_EXPORT session_proxy + { + friend struct session; + // 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 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(). + struct TORRENT_EXPORT session : session_handle + { + // 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. + // + // The overloads taking ``flags`` can be used to start a session in + // paused mode (by passing in ``session::paused``). Note that + // ``add_default_plugins`` do not have an affect on constructors that + // take a session_params object. It already contains the plugins to use. + explicit session(session_params const& params); + explicit session(session_params&& params); + session(session_params const& params, session_flags_t flags); + session(session_params&& params, session_flags_t flags); + session(); + + // Overload of the constructor that takes an external io_context to run + // the session object on. This is primarily useful for tests that may want + // to run multiple sessions on a single io_context, or low resource + // systems where additional threads are expensive and sharing an + // io_context with other events is fine. + // + // .. warning:: + // The session object does not cleanly terminate with an external + // ``io_context``. The ``io_context::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_context, then + // destruct the session_proxy object. + session(session_params&& params, io_context& ios); + session(session_params const& params, io_context& ios); + session(session_params&& params, io_context& ios, session_flags_t); + session(session_params const& params, io_context& ios, session_flags_t); + + // hidden + session(session&&); + session& operator=(session&&) &; + + // hidden + session(session const&) = delete; + session& operator=(session const&) = delete; + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // 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. + TORRENT_DEPRECATED + session(settings_pack&& pack, session_flags_t const flags); + TORRENT_DEPRECATED + session(settings_pack const& pack, session_flags_t const flags); + explicit session(settings_pack&& pack) : session(std::move(pack), add_default_plugins) {} + explicit session(settings_pack const& pack) : session(pack, add_default_plugins) {} + + // overload of the constructor that takes an external io_context to run + // the session object on. This is primarily useful for tests that may want + // to run multiple sessions on a single io_context, or low resource + // systems where additional threads are expensive and sharing an + // io_context with other events is fine. + // + // .. warning:: + // The session object does not cleanly terminate with an external + // ``io_context``. The ``io_context::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_context, then + // destruct the session_proxy object. + TORRENT_DEPRECATED + session(settings_pack&&, io_context&, session_flags_t); + TORRENT_DEPRECATED + session(settings_pack const&, io_context&, session_flags_t); + session(settings_pack&& pack, io_context& ios) : session(std::move(pack), ios, add_default_plugins) {} + session(settings_pack const& pack, io_context& ios) : session(pack, ios, add_default_plugins) {} + +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // TORRENT_ABI_VERSION + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + 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); + + 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); + +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#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:: + // + // struct session_proxy {}; + session_proxy abort(); + + private: + + void start(session_flags_t, session_params&& params, io_context* ios); + +#if TORRENT_ABI_VERSION <= 2 + void start(session_flags_t flags, settings_pack&& sp, io_context* ios); +#endif + + void start(session_params const& params, io_context* ios) = delete; +#if TORRENT_ABI_VERSION <= 2 + void start(session_flags_t flags, settings_pack const& sp, io_context* ios) = delete; +#endif + + // 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/docs/include/libtorrent/session_handle.hpp b/docs/include/libtorrent/session_handle.hpp new file mode 100644 index 0000000..ad40a4b --- /dev/null +++ b/docs/include/libtorrent/session_handle.hpp @@ -0,0 +1,1126 @@ +/* + +Copyright (c) 2014-2018, Steven Siloti +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2015-2018, Alden Torres +Copyright (c) 2020, AllSeeingEyeTolledEweSew +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 // for shared_ptr + +#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/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_context.hpp" +#include "libtorrent/session_types.hpp" +#include "libtorrent/portmap.hpp" // for portmap_protocol + +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/kademlia/announce_flags.hpp" + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/kademlia/dht_settings.hpp" +#endif + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/session_settings.hpp" +#include +#endif + +#include "libtorrent/extensions.hpp" +#include "libtorrent/session_types.hpp" // for session_flags_t + +namespace libtorrent { + + struct 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 struct 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; + +#if TORRENT_ABI_VERSION <= 2 + // saves dht_settings. All DHT settings are now part of the main + // settings_pack, and saved by setting the save_settings flag + TORRENT_DEPRECATED static constexpr save_state_flags_t save_dht_settings = 1_bit; +#endif + + // 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 + TORRENT_DEPRECATED static constexpr save_state_flags_t save_encryption_settings = 3_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_as_map = 4_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_proxy = 5_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_i2p_proxy = 6_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_dht_proxy = 7_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_peer_proxy = 8_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_web_proxy = 9_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_tracker_proxy = 10_bit; +#endif + + // load or save state from plugins + static constexpr save_state_flags_t save_extension_state = 11_bit; + + // load or save the IP filter set on the session + static constexpr save_state_flags_t save_ip_filter = 12_bit; + +#if TORRENT_ABI_VERSION <= 2 + // deprecated in 2.0 + // instead of these functions, use session_state() below, and restore + // state using the session_params on session construction. + + // 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(). + TORRENT_DEPRECATED + void save_state(entry& e, save_state_flags_t flags = save_state_flags_t::all()) const; + TORRENT_DEPRECATED + void load_state(bdecode_node const& e, save_state_flags_t flags = save_state_flags_t::all()); +#endif + + // returns the current session state. This can be passed to + // write_session_params() to save the state to disk and restored using + // read_session_params() when constructing a new session. The kind of + // state that's included is all settings, the DHT routing table, possibly + // plugin-specific state. + // the flags parameter can be used to only save certain parts of the + // session state + session_params session_state(save_state_flags_t flags = save_state_flags_t::all()) 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. + 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_context& get_context(); + + // set the DHT state for the session. This will be taken into account the + // next time the DHT is started, as if it had been passed in via the + // session_params on startup. + void set_dht_state(dht::dht_state const& st); + void set_dht_state(dht::dht_state&& st); + + // ``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); + + // 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 + , client_data_t userdata = {}); +#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 1.2 + TORRENT_DEPRECATED + void get_torrent_status(std::vector* ret + , std::function const& pred + , status_flags_t flags = {}) const; + + // ``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 + +#if TORRENT_ABI_VERSION <= 2 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + // ``set_dht_settings`` sets some parameters available to the dht node. + // See dht_settings for more information. + // + // ``get_dht_settings()`` returns the current settings + void set_dht_settings(dht::dht_settings const& settings); + dht::dht_settings get_dht_settings() const; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + + // ``is_dht_running()`` returns true if the DHT support has been started + // and false otherwise. + bool is_dht_running() 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, client_data_t userdata = {}); + +#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`: reference-Plugins.html + void add_extension(std::function( + torrent_handle const&, client_data_t)> ext); + void add_extension(std::shared_ptr ext); + +#if TORRENT_ABI_VERSION == 1 + // 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; +#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 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; + +#if TORRENT_ABI_VERSION <= 2 + // this will add common extensions like ut_pex, ut_metadata, lt_tex + // smart_ban and possibly others. + TORRENT_DEPRECATED static constexpr session_flags_t add_default_plugins = 0_bit; +#endif + +#if TORRENT_ABI_VERSION == 1 + // this will start features like DHT, local service discovery, UPnP + // and NAT-PMP. + TORRENT_DEPRECATED static constexpr session_flags_t start_default_features = 1_bit; +#endif + + // when set, the session will start paused. Call + // session_handle::resume() to start + static constexpr session_flags_t paused = 2_bit; + + // ``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. + // + // remove_torrent() is non-blocking, but will remove the torrent from the + // session synchronously. Calling session_handle::add_torrent() immediately + // afterward with the same torrent will succeed. Note that this creates a + // new handle which is not equal to the removed one. + // + // 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``. Once the torrent is deleted, a + // torrent_deleted_alert is posted. + // + // The torrent_handle remains valid for some time after remove_torrent() is + // called. It will become invalid only after all libtorrent tasks (such as + // I/O tasks) release their references to the torrent. Until this happens, + // torrent_handle::is_valid() will return true, and other calls such + // as torrent_handle::status() will succeed. Because of this, and because + // remove_torrent() is non-blocking, the following sequence usually + // succeeds (does not throw system_error): + // .. code:: c++ + // + // session.remove_handle(handle); + // handle.save_resume_data(); + // + // 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&, remove_flags_t = {}); + + // 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&); + void apply_settings(settings_pack&&); + settings_pack get_settings() const; + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // deprecated in libtorrent 1.1. use settings_pack instead + TORRENT_DEPRECATED + void set_pe_settings(pe_settings const&); + TORRENT_DEPRECATED + pe_settings get_pe_settings() const; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // ``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&); + 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 + // 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/docs/include/libtorrent/session_params.hpp b/docs/include/libtorrent/session_params.hpp new file mode 100644 index 0000000..49b0eb2 --- /dev/null +++ b/docs/include/libtorrent/session_params.hpp @@ -0,0 +1,156 @@ +/* + +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2019-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_SESSION_PARAMS_HPP_INCLUDED +#define TORRENT_SESSION_PARAMS_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/kademlia/dht_state.hpp" +#include "libtorrent/session_types.hpp" +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/ip_filter.hpp" + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/kademlia/dht_settings.hpp" +#endif + +namespace libtorrent { + +TORRENT_VERSION_NAMESPACE_3 +struct plugin; +TORRENT_VERSION_NAMESPACE_3_END + +struct disk_interface; +struct counters; + +using disk_io_constructor_type = std::function( + io_context&, settings_interface const&, counters&)>; + +TORRENT_VERSION_NAMESPACE_3 + +// 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). Pass a settings_pack to set the + // initial settings when the session starts. + session_params(settings_pack&& sp); // NOLINT + session_params(settings_pack const& sp); // NOLINT + session_params(); + + // hidden + ~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&); + session_params(session_params&&); + session_params& operator=(session_params const&) &; + session_params& operator=(session_params&&) &; + + // The settings to configure the session with + settings_pack settings; + + // the plugins to add to the session as it is constructed + std::vector> extensions; + +#if TORRENT_ABI_VERSION <= 2 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // this is deprecated. Use the dht_* settings instead. + dht::dht_settings dht_settings; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + + // DHT node ID and node addresses to bootstrap the DHT with. + dht::dht_state dht_state; + + // function object to construct the storage object for DHT items. + dht::dht_storage_constructor_type dht_storage_constructor; + + // function object to create the disk I/O subsystem. Defaults to + // default_disk_io_constructor. + disk_io_constructor_type disk_io_constructor; + + // this container can be used by extensions/plugins to store settings. It's + // primarily here to make it convenient to save and restore state across + // sessions, using read_session_params() and write_session_params(). + std::map ext_state; + + // the IP filter to use for the session. This restricts which peers are allowed + // to connect. As if passed to set_ip_filter(). + libtorrent::ip_filter ip_filter; +}; + +TORRENT_VERSION_NAMESPACE_3_END + +// These functions serialize and de-serialize a ``session_params`` object to and +// from bencoded form. The session_params object is used to initialize a new +// session using the state from a previous one (or by programmatically configure +// the session up-front). +// The flags parameter can be used to only save and load certain aspects of the +// session's state. +// The ``_buf`` suffix indicates the function operates on buffer rather than the +// bencoded structure. +// The torrents in a session are not part of the session_params state, they have +// to be restored separately. +TORRENT_EXPORT session_params read_session_params(bdecode_node const& e + , save_state_flags_t flags = save_state_flags_t::all()); +TORRENT_EXPORT session_params read_session_params(span buf + , save_state_flags_t flags = save_state_flags_t::all()); +TORRENT_EXPORT entry write_session_params(session_params const& sp + , save_state_flags_t flags = save_state_flags_t::all()); +TORRENT_EXPORT std::vector write_session_params_buf(session_params const& sp + , save_state_flags_t flags = save_state_flags_t::all()); + +} + +#endif diff --git a/docs/include/libtorrent/session_settings.hpp b/docs/include/libtorrent/session_settings.hpp new file mode 100644 index 0000000..a4ae9d6 --- /dev/null +++ b/docs/include/libtorrent/session_settings.hpp @@ -0,0 +1,118 @@ +/* + +Copyright (c) 2006-2007, 2010, 2013-2020, Arvid Norberg +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_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 { + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + using dht_settings = dht::dht_settings; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + 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/docs/include/libtorrent/session_stats.hpp b/docs/include/libtorrent/session_stats.hpp new file mode 100644 index 0000000..d36fb86 --- /dev/null +++ b/docs/include/libtorrent/session_stats.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2015, 2017-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_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 + TORRENT_DEPRECATED static constexpr metric_type_t type_counter = metric_type_t::counter; + TORRENT_DEPRECATED static constexpr metric_type_t 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/docs/include/libtorrent/session_status.hpp b/docs/include/libtorrent/session_status.hpp new file mode 100644 index 0000000..c7f7436 --- /dev/null +++ b/docs/include/libtorrent/session_status.hpp @@ -0,0 +1,238 @@ +/* + +Copyright (c) 2006, 2008-2011, 2013-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Falcosc +Copyright (c) 2017, Falco +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/docs/include/libtorrent/session_types.hpp b/docs/include/libtorrent/session_types.hpp new file mode 100644 index 0000000..f794cdc --- /dev/null +++ b/docs/include/libtorrent/session_types.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2017, 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_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; + + // The flags type used to specify options to removing files of torrents + using remove_flags_t = flags::bitfield_flag; + + // hidden + using reopen_network_flags_t = flags::bitfield_flag; +} + +#endif + diff --git a/docs/include/libtorrent/settings_pack.hpp b/docs/include/libtorrent/settings_pack.hpp new file mode 100644 index 0000000..d85faff --- /dev/null +++ b/docs/include/libtorrent/settings_pack.hpp @@ -0,0 +1,2176 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2018, TheOriginalWinCat +Copyright (c) 2019, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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(settings_pack const& sett, entry::dictionary_type& out); + TORRENT_EXTRA_EXPORT settings_pack non_default_settings(aux::session_settings const& 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); + + // 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 common interface to settings_pack and the internal representation of + // settings. + struct TORRENT_EXPORT settings_interface + { + virtual void set_str(int name, std::string val) = 0; + virtual void set_int(int name, int val) = 0; + virtual void set_bool(int name, bool val) = 0; + virtual bool has_val(int name) const = 0; + + virtual std::string const& get_str(int name) const = 0; + virtual int get_int(int name) const = 0; + virtual bool get_bool(int name) const = 0; + + template + // hidden + void set_int(int name, flags::bitfield_flag const val) + { set_int(name, static_cast(static_cast(val))); } + + // hidden + // these are here just to suppress the warning about virtual destructors + // internal + settings_interface() = default; + settings_interface(settings_interface const&) = default; + settings_interface(settings_interface&&) = default; + settings_interface& operator=(settings_interface const&) = default; + settings_interface& operator=(settings_interface&&) = default; + protected: + ~settings_interface() = default; + }; + + // 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 final : settings_interface + { + 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) override; + void set_int(int name, int val) override; + void set_bool(int name, bool val) override; + 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 override; + + // 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 override; + int get_int(int name) const override; + bool get_bool(int name) const override; + + // 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 + }; + + // internal + template + void for_each(Fun&& f) const + { + for (auto const& s : m_strings) f(s.first, s.second); + for (auto const& i : m_ints) f(i.first, i.second); + for (auto const& b : m_bools) f(b.first, b.second); + } + + // 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. + // + // When outgoing interfaces are specified, incoming connections or + // packets sent to a local interface or IP that's *not* in this list + // will be rejected with a peer_blocked_alert with + // ``invalid_local_interface`` as the reason. + // + // Note that these are just interface/adapter names or IP addresses. + // There are no ports specified in this list. IPv6 addresses without + // port should be specified without enclosing ``[``, ``]``. + 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, + +#if TORRENT_ABI_VERSION == 1 + // 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 TORRENT_DEPRECATED_ENUM, + 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, + + // 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 TORRENT_DEPRECATED_ENUM, + coalesce_writes TORRENT_DEPRECATED_ENUM, +#else + deprecated_use_read_cache, + deprecated_use_write_cache, + deprecated_flush_write_cache, + deprecated_coalesce_reads, + deprecated_coalesce_writes, +#endif + + // 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 + +#if TORRENT_ABI_VERSION <= 2 + // ``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 TORRENT_DEPRECATED_ENUM, +#else + deprecated_volatile_read_cache, +#endif + +#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 + + // Enables incoming and outgoing, TCP and uTP peer connections. + // ``false`` is disabled and ``true`` is enabled. When outgoing + // connections are disabled, libtorrent will simply not make + // outgoing peer connections with the specific transport protocol. + // Disabled incoming peer connections will simply be rejected. + // These options only apply to peer connections, not tracker- or any + // other kinds of connections. + 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. + // + // * A generic user-agent will be + // used for trackers (except for private torrents). + // * Your local IPv4 and IPv6 address won't be sent as query string + // parameters to private trackers. + // * If announce_ip is configured, it will not be sent to trackers + // * The client version will not be sent to peers in the extension + // handshake. + 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, + +#if TORRENT_ABI_VERSION <= 2 + // 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 TORRENT_DEPRECATED_ENUM, +#else + deprecated_allow_partial_disk_writes, +#endif + +#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 TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // if this is false, don't advertise support for the Tribler merkle + // tree piece message + support_merkle_torrents TORRENT_DEPRECATED_ENUM, +#else + deprecated_support_merkle_torrents, +#endif + + // 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, + + // 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 + dht_restrict_routing_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. + dht_restrict_search_ips, + + // 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. + dht_extended_routing_table, + + // 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. + dht_aggressive_lookups, + + // when set, perform lookups in a way that is slightly more expensive, + // but which minimizes the amount of information leaked about you. + dht_privacy_lookups, + + // 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". + dht_enforce_node_id, + + // ignore DHT messages from parts of the internet we wouldn't expect to + // see any traffic from + dht_ignore_dark_internet, + + // 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. + dht_read_only, + + // 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 and HTTPS web + // seeds will be validated against the system's certificate store + // (as defined by OpenSSL). If the system does not have a + // certificate store, this option may have to be disabled in order + // to get trackers and web seeds to work). + validate_https_trackers, + + // when enabled, tracker and web seed requests are subject to + // certain restrictions. + // + // An HTTP(s) tracker requests to localhost (loopback) + // must have the request path start with "/announce". This is the + // conventional bittorrent tracker request. Any other HTTP(S) + // tracker request to loopback will be rejected. This applies to + // trackers that redirect to loopback as well. + // + // Web seeds that end up on the client's local network (i.e. in a + // private IP address range) may not include query string arguments. + // This applies to web seeds redirecting to the local network as + // well. + // + // Web seeds on global IPs (i.e. not local network) may not redirect + // to a local network address + ssrf_mitigation, + + // when disabled, any tracker or web seed with an IDNA hostname + // (internationalized domain name) is ignored. This is a security + // precaution to avoid various unicode encoding attacks that might + // happen at the application level. + allow_idna, + + // when set to true, enables the attempt to use SetFileValidData() + // to pre-allocate disk space. This system call will only work when + // running with Administrator privileges on Windows, and so this + // setting is only relevant in that scenario. Using + // SetFileValidData() poses a security risk, as it may reveal + // previously deleted information from the disk. + enable_set_file_valid_data, + + // When using a SOCKS5 proxy, UDP traffic is routed through the + // proxy by sending a UDP ASSOCIATE command. If this option is true, + // the UDP ASSOCIATE command will include the IP address and + // listen port to the local UDP socket. This indicates to the proxy + // which source endpoint to expect our packets from. The benefit is + // that incoming packets can be forwarded correctly, before any + // outgoing packets are sent. The risk is that if there's a NAT + // between the client and the proxy, the IP address specified in the + // protocol may not be valid from the proxy's point of view. + socks5_udp_send_local_ep, + + 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, + +#if TORRENT_ABI_VERSION == 1 + // ``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). + // + // 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 TORRENT_DEPRECATED_ENUM, + + // Disk buffers are allocated using a pool allocator, the number of + // blocks that are allocated at a time when the pool needs to grow can + // be specified in ``cache_buffer_chunk_size``. Lower numbers saves + // memory at the expense of more heap allocations. If it is set to 0, + // the effective chunk size is proportional to the total cache size, + // attempting to strike a good balance between performance and memory + // usage. It defaults to 0. + cache_buffer_chunk_size TORRENT_DEPRECATED_ENUM, + + // ``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. + cache_expiry TORRENT_DEPRECATED_ENUM, +#else + deprecated_cache_size, + deprecated_cache_buffer_chunk_size, + deprecated_cache_expiry, +#endif + + // 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. + // write_through + // flush pieces to disk as they complete validation. + // + // 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, + // ``0x04`` represents Lower Effort. For more details see `RFC 8622`_. + // + // .. _`RFC 8622`: http://www.faqs.org/rfcs/rfc8622.html + 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 + +#if TORRENT_ABI_VERSION <= 2 + // ``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, +#else + deprecated_read_cache_line_size, + deprecated_write_cache_line_size, +#endif + + // ``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 + + // 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. + 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, + + // ``hashing_threads`` is the number of disk I/O threads to use for + // piece hash verification. These threads are *in addition* to the + // regular disk I/O threads specified by settings_pack::aio_threads. + // The hasher threads do not only compute hashes, but also perform + // the read from disk. On storage optimal for sequential access, + // such as hard drives, this setting should probably be set to 1. + hashing_threads, + + // 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, + +#if TORRENT_ABI_VERSION == 1 + // 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, +#else + deprecated_cache_size_volatile, +#endif + + // 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 240 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, + + // the maximum number of peers to send in a reply to ``get_peers`` + dht_max_peers_reply, + + // 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 + dht_search_branching, + + // 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. + dht_max_fail_count, + + // 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. + dht_max_torrents, + + // max number of items the DHT will store + dht_max_dht_items, + + // the max number of peers to store per torrent (for the DHT) + dht_max_peers, + + // the max number of torrents to return in a torrent search query to the + // DHT + dht_max_torrent_search_reply, + + // 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. + dht_block_timeout, + + // the max number of packets per second a DHT node is allowed to send + // without getting banned. + dht_block_ratelimit, + + // the number of seconds a immutable/mutable item will be expired. + // default is 0, means never expires. + dht_item_lifetime, + + // 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). + dht_sample_infohashes_interval, + + // 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 + dht_max_infohashes_sample_count, + + // ``max_piece_count`` is the maximum allowed number of pieces in + // metadata received via magnet links. Loading large torrents (with + // more pieces than the default limit) may also require passing in + // a higher limit to read_resume_data() and + // torrent_info::parse_info_section(), if those are used. + max_piece_count, + + // when receiving metadata (torrent file) from peers, this is the + // max number of bencoded tokens we're willing to parse. This limit + // is meant to prevent DoS attacks on peers. For very large + // torrents, this limit may have to be raised. + metadata_token_limit, + + max_int_setting_internal + }; + + // hidden + constexpr static int num_string_settings = int(max_string_setting_internal) - int(string_type_base); + constexpr static int num_bool_settings = int(max_bool_setting_internal) - int(bool_type_base); + constexpr static int 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, + + write_through = 3, + }; + + 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/docs/include/libtorrent/sha1.hpp b/docs/include/libtorrent/sha1.hpp new file mode 100644 index 0000000..185d0ee --- /dev/null +++ b/docs/include/libtorrent/sha1.hpp @@ -0,0 +1,43 @@ +/* +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_CNG \ + && !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/docs/include/libtorrent/sha1_hash.hpp b/docs/include/libtorrent/sha1_hash.hpp new file mode 100644 index 0000000..41cfd74 --- /dev/null +++ b/docs/include/libtorrent/sha1_hash.hpp @@ -0,0 +1,315 @@ +/* + +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +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_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 { + + // 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 or right ``n`` bits. + digest32& operator<<=(int n) & noexcept; + digest32& operator>>=(int n) & noexcept; + + // 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()); + } + +#if TORRENT_USE_IOSTREAM + // print a sha1_hash object to an ostream as 40 hexadecimal digits + friend std::ostream& operator<<(std::ostream& os, digest32 const& val) + { val.stream_out(os); return os; } + + // read 40 hexadecimal digits from an istream into a sha1_hash + friend std::istream& operator>>(std::istream& is, digest32& val) + { val.stream_in(is); return is; } +#endif // TORRENT_USE_IOSTREAM + + private: + +#if TORRENT_USE_IOSTREAM + void stream_in(std::istream& is); + void stream_out(std::ostream& os) const; +#endif // TORRENT_USE_IOSTREAM + + 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>; + using sha256_hash = digest32<256>; + +#if TORRENT_USE_IOSTREAM + extern template void digest32<160>::stream_out(std::ostream&) const; + extern template void digest32<256>::stream_out(std::ostream&) const; + extern template void digest32<160>::stream_in(std::istream&); + extern template void digest32<256>::stream_in(std::istream&); +#endif // TORRENT_USE_IOSTREAM + + extern template digest32<160>& digest32<160>::operator<<=(int) & noexcept; + extern template digest32<256>& digest32<256>::operator<<=(int) & noexcept; + extern template digest32<160>& digest32<160>::operator>>=(int) & noexcept; + extern template digest32<256>& digest32<256>::operator>>=(int) & noexcept; +} + +namespace std { + template + struct hash> + { + std::size_t operator()(libtorrent::digest32 const& k) const + { + std::size_t ret; + static_assert(N >= sizeof(ret) * 8, "hash is not defined for small digests"); + // this is OK because digest32 is already a hash + std::memcpy(&ret, k.data(), sizeof(ret)); + return ret; + } + }; +} + +#endif // TORRENT_SHA1_HASH_HPP_INCLUDED diff --git a/docs/include/libtorrent/sha256.hpp b/docs/include/libtorrent/sha256.hpp new file mode 100644 index 0000000..df96d26 --- /dev/null +++ b/docs/include/libtorrent/sha256.hpp @@ -0,0 +1,33 @@ +// SHA-256. Adapted from LibTomCrypt. This code is Public Domain + +#ifndef TORRENT_SHA256_HPP_INCLUDED +#define TORRENT_SHA256_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if !defined TORRENT_USE_LIBGCRYPT \ + && !TORRENT_USE_COMMONCRYPTO \ + && !TORRENT_USE_CNG \ + && !TORRENT_USE_CRYPTOAPI_SHA_512 \ + && !defined TORRENT_USE_LIBCRYPTO + +#include + +namespace libtorrent { + + struct sha256_ctx + { + std::uint64_t length; + std::uint32_t state[8]; + std::uint32_t curlen; + std::uint8_t buf[64]; + }; + + TORRENT_EXTRA_EXPORT void SHA256_init(sha256_ctx& md); + TORRENT_EXTRA_EXPORT void SHA256_update(sha256_ctx& md + , std::uint8_t const* in, size_t len); + TORRENT_EXTRA_EXPORT void SHA256_final(std::uint8_t* digest, sha256_ctx& md); +} + +#endif +#endif diff --git a/docs/include/libtorrent/sliding_average.hpp b/docs/include/libtorrent/sliding_average.hpp new file mode 100644 index 0000000..b2c813f --- /dev/null +++ b/docs/include/libtorrent/sliding_average.hpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2010, 2014, 2016-2019, Arvid Norberg +Copyright (c) 2019, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 // for is_integral + +#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/docs/include/libtorrent/socket.hpp b/docs/include/libtorrent/socket.hpp new file mode 100644 index 0000000..32c8c9c --- /dev/null +++ b/docs/include/libtorrent/socket.hpp @@ -0,0 +1,298 @@ +/* + +Copyright (c) 2003-2004, 2006-2010, 2012, 2014-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2018, Alexandre Janniaux +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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_/noexcept_movable.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 +struct tcp : sim::asio::ip::tcp { + tcp(sim::asio::ip::tcp const& p) : sim::asio::ip::tcp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; +struct udp : sim::asio::ip::udp { + udp(sim::asio::ip::udp const& p) : sim::asio::ip::udp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; + using sim::asio::async_write; + using sim::asio::async_read; + using true_tcp_socket = sim::asio::ip::tcp::socket; +#else +struct tcp : boost::asio::ip::tcp { + tcp(boost::asio::ip::tcp const& p) : boost::asio::ip::tcp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; +struct udp : boost::asio::ip::udp { + udp(boost::asio::ip::udp const& p) : boost::asio::ip::udp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; + using boost::asio::async_write; + using boost::asio::async_read; + using true_tcp_socket = boost::asio::ip::tcp::socket; +#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(int 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(tos_t const 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; + }; + +#ifdef IP_DSCP_TRAFFIC_TYPE + struct dscp_traffic_type + { + explicit dscp_traffic_type(DWORD val) : m_value(val) {} + template + int level(Protocol const&) const { return IP_DSCP_TRAFFIC_TYPE; } + template + int name(Protocol const&) const { return DSCP_TRAFFIC_TYPE; } + template + DWORD const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + DWORD m_value; + }; +#endif + +#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_PROBE : 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/docs/include/libtorrent/socket_io.hpp b/docs/include/libtorrent/socket_io.hpp new file mode 100644 index 0000000..b0b6451 --- /dev/null +++ b/docs/include/libtorrent/socket_io.hpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2009, 2013-2019, Arvid Norberg +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016-2017, 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_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 aux { + + 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_uint(), out); + } + else if (a.is_v6()) + { + for (auto b : a.to_v6().to_bytes()) + write_uint8(b, out); + } + } + + template + address_v4 read_v4_address(InIt&& in) + { + std::uint32_t const ip = read_uint32(in); + return address_v4(ip); + } + + template + address_v6 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 aux + +} + +#endif diff --git a/docs/include/libtorrent/socket_type.hpp b/docs/include/libtorrent/socket_type.hpp new file mode 100644 index 0000000..ae15d31 --- /dev/null +++ b/docs/include/libtorrent/socket_type.hpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2019-2020, Arvid Norberg +Copyright (c) 2020, 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_SOCKET_TYPE_HPP +#define TORRENT_SOCKET_TYPE_HPP + +#include "libtorrent/config.hpp" +#include + +namespace libtorrent { + +// A type describing kinds of sockets involved in various operations or events. +enum class socket_type_t : std::uint8_t { + tcp, + socks5, + http, + utp, + i2p, + tcp_ssl, + socks5_ssl, + http_ssl, + utp_ssl, + +#if TORRENT_ABI_VERSION <= 2 + udp TORRENT_DEPRECATED_ENUM = utp, +#endif +}; + +// return a short human readable name for types of socket +// TODO: move to aux +char const* socket_type_name(socket_type_t); + +} + +#endif diff --git a/docs/include/libtorrent/socks5_stream.hpp b/docs/include/libtorrent/socks5_stream.hpp new file mode 100644 index 0000000..5e8c11c --- /dev/null +++ b/docs/include/libtorrent/socks5_stream.hpp @@ -0,0 +1,561 @@ +/* + +Copyright (c) 2007, 2009-2010, 2013-2020, Arvid Norberg +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_SOCKS5_STREAM_HPP_INCLUDED +#define TORRENT_SOCKS5_STREAM_HPP_INCLUDED + +#include + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" // for is_ip_address +#include "libtorrent/assert.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/string_util.hpp" // for to_string +#include "libtorrent/socket_io.hpp" + +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 +} + +namespace boost { +namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + +} +} + +namespace libtorrent { + +// 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 + }; + + socks5_stream(socks5_stream&&) = default; + explicit socks5_stream(io_context& io_context) + : proxy_base(io_context) + , 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(!aux::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 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 + + ADD_OUTSTANDING_ASYNC("socks5_stream::name_lookup"); + m_resolver.async_resolve(m_hostname, to_string(m_port).data(), wrap_allocator( + [this](error_code const& ec, tcp::resolver::results_type ips, Handler hn) { + name_lookup(ec, std::move(ips), std::move(hn)); + }, std::move(handler))); + } + +private: + + template + void name_lookup(error_code const& e, tcp::resolver::results_type ips + , Handler h) + { + COMPLETE_ASYNC("socks5_stream::name_lookup"); + if (handle_error(e, std::move(h))) return; + + auto i = ips.begin(); + if (!m_sock.is_open()) + { + error_code ec; + m_sock.open(i->endpoint().protocol(), ec); + if (handle_error(ec, std::move(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(), wrap_allocator( + [this](error_code const& ec, Handler hn) + { connected(ec, std::move(hn)); }, std::move(h))); + } + + template + void connected(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connected"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + 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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake1(ec, std::move(hn)); + }, std::move(h))); + } + else if (m_version == 4) + { + socks_connect(std::move(h)); + } + else + { + std::move(h)(error_code(socks_error::unsupported_version)); + } + } + + template + void handshake1(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake1"); + if (handle_error(e, std::move(h))) return; + + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake2"); + m_buffer.resize(2); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake2(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake2(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake2"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + + char* p = &m_buffer[0]; + int version = read_uint8(p); + int method = read_uint8(p); + + if (version < m_version) + { + std::move(h)(error_code(socks_error::unsupported_version)); + return; + } + + if (method == 0) + { + socks_connect(std::move(h)); + } + else if (method == 2) + { + if (m_user.empty()) + { + std::move(h)(error_code(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), wrap_allocator( + [this](error_code const& ec, std::size_t const, Handler hn) { + handshake3(ec, std::move(hn)); + }, std::move(h))); + } + else + { + std::move(h)(error_code(socks_error::unsupported_authentication_method)); + return; + } + } + + template + void handshake3(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake3"); + if (handle_error(e, std::move(h))) return; + + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake4"); + m_buffer.resize(2); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake4(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake4(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake4"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + + char* p = &m_buffer[0]; + int version = read_uint8(p); + int status = read_uint8(p); + + if (version != 1) + { + std::move(h)(error_code(socks_error::unsupported_authentication_version)); + return; + } + + if (status != 0) + { + std::move(h)(error_code(socks_error::authentication_error)); + return; + } + + std::vector().swap(m_buffer); + socks_connect(std::move(h)); + } + + template + void socks_connect(Handler h) + { + using namespace libtorrent::aux; + + if (m_version == 5) + { + // send SOCKS5 connect command + m_buffer.resize(6 + (!m_dst_name.empty() + ? m_dst_name.size() + 1 + :(aux::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(aux::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 (!aux::is_v4(m_remote_endpoint)) + { + std::move(h)(error_code(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_uint(), p); + std::copy(m_user.begin(), m_user.end(), p); + p += m_user.size(); + write_uint8(0, p); // 0-terminator + } + else + { + std::move(h)(error_code(socks_error::unsupported_version)); + return; + } + + ADD_OUTSTANDING_ASYNC("socks5_stream::connect1"); + async_write(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + connect1(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connect1(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connect1"); + if (handle_error(e, std::move(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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + connect2(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connect2(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connect2"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + + 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) + { + std::move(h)(error_code(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; + } + std::move(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); + std::move(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 + { + std::move(h)(error_code(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) + , wrap_allocator([this](error_code const& ec, std::size_t, Handler hn) { + connect3(ec, std::move(hn)); + }, std::move(h))); + } + else if (m_version == 4) + { + if (version != 0) + { + std::move(h)(error_code(socks_error::general_failure)); + return; + } + + // access granted + if (response == 90) + { + std::vector().swap(m_buffer); + std::move(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; + } + std::move(h)(ec); + } + } + + template + void connect3(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connect3"); + using namespace libtorrent::aux; + + if (handle_error(e, std::move(h))) return; + + std::vector().swap(m_buffer); + std::move(h)(e); + } + + // 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; +}; + +} + +#endif diff --git a/docs/include/libtorrent/span.hpp b/docs/include/libtorrent/span.hpp new file mode 100644 index 0000000..6a9b963 --- /dev/null +++ b/docs/include/libtorrent/span.hpp @@ -0,0 +1,194 @@ +/* + +Copyright (c) 2016-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2019, Michael Cronenworth +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/docs/include/libtorrent/ssl.hpp b/docs/include/libtorrent/ssl.hpp new file mode 100644 index 0000000..e91deb3 --- /dev/null +++ b/docs/include/libtorrent/ssl.hpp @@ -0,0 +1,186 @@ +/* + +Copyright (c) 2020, Paul-Louis Ageneau +Copyright (c) 2018, Alexandre Janniaux +Copyright (c) 2020, Steven Siloti +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_SSL_HPP_INCLUDED +#define TORRENT_SSL_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/export.hpp" + +#if TORRENT_USE_SSL + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#ifndef BOOST_NO_EXCEPTIONS +#include +#endif + +#ifdef TORRENT_USE_OPENSSL +#include // for OPENSSL_VERSION_NUMBER +#if OPENSSL_VERSION_NUMBER < 0x1000000fL +#error OpenSSL too old, use a recent version with SNI support +#endif +#ifdef TORRENT_WINDOWS +// because openssl includes winsock.h, we must include winsock2.h first +#include +#endif +#include +#include +#include +#include +#endif + +#ifdef TORRENT_USE_GNUTLS +#include +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#endif + +#include +#include +#include +#include + +namespace libtorrent { +namespace ssl { + +using error_code = boost::system::error_code; + +#if defined TORRENT_USE_OPENSSL +#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 +using boost::asio::ssl::verify_context; +#if BOOST_VERSION >= 107300 +using boost::asio::ssl::host_name_verification; +#else +using host_name_verification = boost::asio::ssl::rfc2818_verification; +#endif + +using native_context_type = SSL_CTX*; +using native_stream_type = SSL*; +using context_handle_type = native_context_type; +using stream_handle_type = native_stream_type; + +typedef int (*server_name_callback_type)(SSL* s, int*, void* arg); + +#elif defined TORRENT_USE_GNUTLS +using boost::asio::gnutls::context; +using boost::asio::gnutls::stream_base; +using boost::asio::gnutls::stream; +using boost::asio::gnutls::verify_context; +using boost::asio::gnutls::host_name_verification; + +using native_context_type = context::native_handle_type; +using native_stream_type = stream_base::native_handle_type; +using context_handle_type = context*; +using stream_handle_type = stream_base*; + +typedef bool (*server_name_callback_type)(stream_handle_type handle, std::string const& name, void* arg); + +#endif + +namespace error { + +#if defined TORRENT_USE_OPENSSL +using boost::asio::error::get_ssl_category; +using boost::asio::ssl::error::get_stream_category; +#elif defined TORRENT_USE_GNUTLS +using boost::asio::gnutls::error::get_ssl_category; +using boost::asio::gnutls::error::get_stream_category; +#endif + +} + +inline context_handle_type get_handle(context &c) +{ +#if defined TORRENT_USE_OPENSSL + return c.native_handle(); +#elif defined TORRENT_USE_GNUTLS + return &c; +#endif +} + +template +stream_handle_type get_handle(stream& s) +{ +#if defined TORRENT_USE_OPENSSL + return s.native_handle(); +#elif defined TORRENT_USE_GNUTLS + return &s; +#endif +} + +template +context_handle_type get_context_handle(stream& s) +{ +#if defined TORRENT_USE_OPENSSL + return SSL_get_SSL_CTX(s.native_handle()); +#elif defined TORRENT_USE_GNUTLS + return &s.get_context(); +#endif +} + +TORRENT_EXTRA_EXPORT void set_trust_certificate(native_context_type nc, string_view pem, error_code &ec); + +TORRENT_EXTRA_EXPORT void set_server_name_callback(context_handle_type c, server_name_callback_type cb, void* arg, error_code& ec); +TORRENT_EXTRA_EXPORT void set_host_name(stream_handle_type s, std::string const& name, error_code& ec); + +TORRENT_EXTRA_EXPORT void set_context(stream_handle_type s, context_handle_type c); +TORRENT_EXTRA_EXPORT bool has_context(stream_handle_type s, context_handle_type c); +TORRENT_EXTRA_EXPORT context_handle_type get_context(stream_handle_type s); + +} // ssl +} // libtorrent + +#endif // TORRENT_USE_SSL + +#endif diff --git a/docs/include/libtorrent/ssl_stream.hpp b/docs/include/libtorrent/ssl_stream.hpp new file mode 100644 index 0000000..7a18f8d --- /dev/null +++ b/docs/include/libtorrent/ssl_stream.hpp @@ -0,0 +1,354 @@ +/* + +Copyright (c) 2008, 2010-2020, Arvid Norberg +Copyright (c) 2018, Alexandre Janniaux +Copyright (c) 2018, Alden Torres +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_SSL + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/ssl.hpp" + +#include + +#include + +namespace libtorrent { + +template +struct ssl_stream +{ + ssl_stream(io_context& io_context, ssl::context& ctx) + : m_sock(new ssl::stream(io_context, ctx)) + {} + + template + ssl_stream(S&& s, ssl::context& ctx) + : m_sock(new ssl::stream(std::forward(s), ctx)) + {} + + ssl_stream(ssl_stream&&) = default; + + using sock_type = typename 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 + + ssl::stream_handle_type handle() + { + return ssl::get_handle(*m_sock); + } + + ssl::context_handle_type context_handle() + { + return ssl::get_context_handle(*m_sock); + } + + void set_host_name(std::string const& name) + { + error_code ec; + set_host_name(name, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } + + void set_host_name(std::string const& name, error_code& ec) + { + ssl::set_host_name(handle(), name, ec); + } + + template + void set_verify_callback(T const& fun, error_code& ec) + { + m_sock->set_verify_callback(fun, ec); + } + + template + void async_connect(endpoint_type const& endpoint, Handler const& h) + { + // the connect is split up in the following steps: + // 1. connect to peer + // 2. perform SSL client handshake + + m_sock->next_layer().async_connect(endpoint, wrap_allocator( + [this](error_code const& ec, Handler hn) { + connected(ec, std::move(hn)); + }, std::move(h))); + } + + template + void async_accept_handshake(Handler const& h) + { + // this is used for accepting SSL connections + m_sock->async_handshake(ssl::stream_base::server, wrap_allocator( + [this](error_code const& ec, Handler hn) { + handshake(ec, std::move(hn)); + }, std::move(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 handler) + { + error_code ec; + m_sock->next_layer().cancel(ec); + m_sock->async_shutdown(std::move(handler)); + } + + void shutdown(error_code& ec) + { + m_sock->shutdown(ec); + } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler handler) + { + m_sock->async_read_some(buffers, std::move(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 + void set_option(SettableSocketOption const& opt, error_code& ec) + { + 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 + void get_option(GettableSocketOption& opt, error_code& ec) + { + 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 + + void non_blocking(bool b, error_code& ec) + { m_sock->next_layer().non_blocking(b, ec); } + + template + void async_write_some(Const_Buffers const& buffers, Handler handler) + { + m_sock->async_write_some(buffers, std::move(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 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 m_sock->next_layer().remote_endpoint(); + } +#endif + + endpoint_type remote_endpoint(error_code& ec) const + { + return m_sock->next_layer().remote_endpoint(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type local_endpoint() const + { + return m_sock->next_layer().local_endpoint(); + } +#endif + + endpoint_type local_endpoint(error_code& ec) const + { + return m_sock->next_layer().local_endpoint(ec); + } + + lowest_layer_type& lowest_layer() + { + return m_sock->lowest_layer(); + } + + lowest_layer_type const& lowest_layer() const + { + return m_sock->lowest_layer(); + } + + next_layer_type& next_layer() + { + return m_sock->next_layer(); + } + + next_layer_type const& next_layer() const + { + return m_sock->next_layer(); + } + +private: + + template + void connected(error_code const& e, Handler h) + { + if (e) + { + h(e); + return; + } + + m_sock->async_handshake(ssl::stream_base::client, wrap_allocator( + [this](error_code const& ec, Handler hn) { + handshake(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake(error_code const& e, Handler h) + { + h(e); + } + + // to make us movable + std::unique_ptr> m_sock; +}; + +} + +#endif // TORRENT_USE_SSL + +#endif diff --git a/docs/include/libtorrent/stack_allocator.hpp b/docs/include/libtorrent/stack_allocator.hpp new file mode 100644 index 0000000..ec94f46 --- /dev/null +++ b/docs/include/libtorrent/stack_allocator.hpp @@ -0,0 +1,96 @@ +/* + +Copyright (c) 2012, 2015-2019, Arvid Norberg +Copyright (c) 2016-2017, 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_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/docs/include/libtorrent/stat.hpp b/docs/include/libtorrent/stat.hpp new file mode 100644 index 0000000..5bdd1f3 --- /dev/null +++ b/docs/include/libtorrent/stat.hpp @@ -0,0 +1,287 @@ +/* + +Copyright (c) 2003-2005, 2007-2012, 2014-2017, 2019, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +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_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/docs/include/libtorrent/stat_cache.hpp b/docs/include/libtorrent/stat_cache.hpp new file mode 100644 index 0000000..c24e7ce --- /dev/null +++ b/docs/include/libtorrent/stat_cache.hpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2010, 2013-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_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/docs/include/libtorrent/storage.hpp b/docs/include/libtorrent/storage.hpp new file mode 100644 index 0000000..fd593aa --- /dev/null +++ b/docs/include/libtorrent/storage.hpp @@ -0,0 +1,7 @@ + +#ifndef TORRENT_STORAGE_HPP_INCLUDED +#define TORRENT_STORAGE_HPP_INCLUDED + +#error "the disk I/O subsystem has been overhauled in libtorrent 2.0. storage_interface is no longer a customization point, customize disk_interface instead" + +#endif diff --git a/docs/include/libtorrent/storage_defs.hpp b/docs/include/libtorrent/storage_defs.hpp new file mode 100644 index 0000000..0d4a9c0 --- /dev/null +++ b/docs/include/libtorrent/storage_defs.hpp @@ -0,0 +1,150 @@ +/* + +Copyright (c) 2006-2007, 2009, 2013-2014, 2016-2020, Arvid Norberg +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_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 { + + 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 mode minimizes + // fragmentation but could be a costly operation. + 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, + + // hidden + mask = 0xf, + + // this is not an enum value, but a flag that can be set in the return + // from async_check_files, in case an existing file was found larger than + // specified in the torrent. i.e. it has garbage at the end + // the status_t field is used for this to preserve ABI. + oversized_file = 0x10, + }; + + // internal + inline status_t operator|(status_t lhs, status_t rhs) + { + return status_t(static_cast(lhs) | static_cast(rhs)); + } + inline status_t operator&(status_t lhs, status_t rhs) + { + return status_t(static_cast(lhs) & static_cast(rhs)); + } + inline status_t operator~(status_t lhs) + { + return status_t(~static_cast(lhs)); + } + + // 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 + + // a parameter pack used to construct the storage for a torrent, used in + // disk_interface + 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 info_hash; + }; +} + +#endif diff --git a/docs/include/libtorrent/string_util.hpp b/docs/include/libtorrent/string_util.hpp new file mode 100644 index 0000000..953c724 --- /dev/null +++ b/docs/include/libtorrent/string_util.hpp @@ -0,0 +1,153 @@ +/* + +Copyright (c) 2012, 2014-2020, Arvid Norberg +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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); + + // same as split_string, but if one sub-string starts with a double quote + // (") separators are ignored until the end double-quote. Unless if the + // separator itself is a double quote. + TORRENT_EXTRA_EXPORT std::pair split_string_quotes( + string_view last, char const sep); + + // removes whitespaces at the beginning of the string, in-place + TORRENT_EXTRA_EXPORT void ltrim(std::string& s); + +#if TORRENT_USE_I2P + + TORRENT_EXTRA_EXPORT bool is_i2p_url(std::string const& url); + +#endif +} + +#endif diff --git a/docs/include/libtorrent/string_view.hpp b/docs/include/libtorrent/string_view.hpp new file mode 100644 index 0000000..5460134 --- /dev/null +++ b/docs/include/libtorrent/string_view.hpp @@ -0,0 +1,109 @@ +/* + +Copyright (c) 2016-2017, 2019-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_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/docs/include/libtorrent/tailqueue.hpp b/docs/include/libtorrent/tailqueue.hpp new file mode 100644 index 0000000..a0b8d3e --- /dev/null +++ b/docs/include/libtorrent/tailqueue.hpp @@ -0,0 +1,194 @@ +/* + +Copyright (c) 2010, 2013-2017, 2019, Arvid Norberg +Copyright (c) 2017, 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_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(tailqueue&& t): m_first(t.m_first), m_last(t.m_last), m_size(t.m_size) + { + t.m_first = nullptr; + t.m_last = nullptr; + t.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/docs/include/libtorrent/time.hpp b/docs/include/libtorrent/time.hpp new file mode 100644 index 0000000..7cf3880 --- /dev/null +++ b/docs/include/libtorrent/time.hpp @@ -0,0 +1,92 @@ +/* + +Copyright (c) 2007, 2009, 2014-2017, 2019-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_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 milliseconds32 = std::chrono::duration>; + 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/docs/include/libtorrent/torrent.hpp b/docs/include/libtorrent/torrent.hpp new file mode 100644 index 0000000..a783112 --- /dev/null +++ b/docs/include/libtorrent/torrent.hpp @@ -0,0 +1,1798 @@ +/* + +Copyright (c) 2003-2020, Arvid Norberg +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2020, Alden Torres +Copyright (c) 2017, Falcosc +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2017, AllSeeingEyeTolledEweSew +Copyright (c) 2018, d-komarov +Copyright (c) 2018-2019, Steven Siloti +Copyright (c) 2019, ghbplayer +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#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/hash_picker.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/bandwidth_limit.hpp" +#include "libtorrent/aux_/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/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/aux_/announce_entry.hpp" +#include "libtorrent/extensions.hpp" // for add_peer_flags_t +#include "libtorrent/ssl.hpp" + +#ifdef TORRENT_SSL_PEERS +// 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; + struct 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; + + // this is set to true when this web seed was created from a redirect + // from a global IP, and SSRF mitigation is enabled. It prevents this + // web seed from resolving to any local network IPs. + bool no_local_ips = 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); + no_local_ips = std::move(rhs.no_local_ips); + 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; + + std::unique_ptr m_hash_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; + + // This is the sum of all non-pad file sizes. In the next major version + // this is stored in file_storage and no longer need to be kept here. + std::int64_t m_size_on_disk = 0; + + // 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 haven'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 + struct TORRENT_EXTRA_EXPORT torrent + : private single_threaded + , private torrent_hot_members + , request_callback + , peer_class_set + , std::enable_shared_from_this + { + // add_torrent_params may contain large merkle trees that are best + // moved. Deleting the const& overload ensures that it's always moved in. + torrent(aux::session_interface& ses, bool session_paused, add_torrent_params&& p); + torrent(aux::session_interface&, bool, add_torrent_params const& p) = delete; + ~torrent() override; + + // This may be called from multiple threads + info_hash_t 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(); + } + + // 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&, client_data_t)> const& ext + , client_data_t 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(); + + void load_merkle_trees(aux::vector, file_index_t> t + , aux::vector, file_index_t> mask + , aux::vector, file_index_t> verified); + + // 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(aux::vector block_hashes + , 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); + void on_disk_read_complete(disk_buffer_holder, storage_error const& + , peer_request const&, std::shared_ptr); + + 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, bool was_paused = false); + 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, file_progress_flags_t flags); + +#if TORRENT_ABI_VERSION == 1 + void use_interface(std::string net_interface); +#endif + + void connect_to_url_seed(std::list::iterator); + bool connect_to_peer(torrent_peer*, 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; + static constexpr web_seed_flag_t no_local_ips = 1_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& tracker_ips + , struct tracker_response const& resp) override; + void tracker_request_error(tracker_request const& r + , error_code const& ec, operation_t op, 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(event_t = event_t::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 + + aux::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); + + // process the v2 block hashes for a piece + boost::tribool on_blocks_hashed(piece_index_t piece + , span block_hashes); + + 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& + , std::vector
    const& + , int port + , protocol_version); + + // 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& + , std::vector
    const& + , int port + , std::list::iterator); + + 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(aux::vector block_hashes + , 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 + // for failures detected with v2 hashes the failing blocks(s) + // are specified in blocks + // *blocks must be sorted in acending order* + void piece_failed(piece_index_t index, std::vector blocks = std::vector()); + + // the peers in "peers" participated in sending a bad piece. If + // "known_bad_peer" is true, we know for sure the peers are guilty, + // otherwise only one may be guilty (meaning we can't unconditionally + // disconnect) + void penalize_peers(std::set const& peers + , piece_index_t index + , bool known_bad_peer); + + // this is the handler for hash failure piece synchronization + // i.e. resetting the piece + void on_piece_sync(piece_index_t piece, std::vector const& blocks); + + // 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; + aux::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; + } + + hash_picker& get_hash_picker() + { + TORRENT_ASSERT(m_hash_picker.get()); + return *m_hash_picker; + } + hash_picker const& get_hash_picker() const + { + TORRENT_ASSERT(m_hash_picker.get()); + return *m_hash_picker; + } + + void need_hash_picker(); + bool has_hash_picker() const + { + return m_hash_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; } + + torrent_info const& torrent_file() const + { return *m_torrent_file; } + + hash_request pick_hashes(peer_connection* peer); + std::vector get_hashes(hash_request const& req) const; + bool add_hashes(hash_request const& req, span hashes); + void hashes_rejected(hash_request const& req); + void verify_block_hashes(piece_index_t index); + + std::shared_ptr get_torrent_file() const; + + std::shared_ptr get_torrent_copy_with_hashes() const; + + std::vector> get_piece_layers() const; + + std::vector trackers() const; + + // 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(resume_data_flags_t const flags, add_torrent_params& ret) 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; } + + error_code initialize_merkle_trees(); + + // 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); + + 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); + + // 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_SSL_PEERS + 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); + 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); + + client_data_t get_userdata() const { return m_userdata; } + + static constexpr int no_gauge_state = 0xf; + + private: + + void on_exception(std::exception const& e); + void on_error(error_code const& ec); + + // 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 + , protocol_version v, std::vector const& peers); + void on_dht_announce_response(protocol_version v, 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; + + // the number of bytes of pad files + std::int64_t m_padding_bytes = 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_SSL_PEERS + std::unique_ptr m_ssl_ctx; + + bool verify_peer_cert(bool const preverified, 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; + +#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 + + // v2 merkle tree for each file + aux::vector m_merkle_trees; + + // 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; + + // 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 + info_hash_t 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 + time_point32 m_started = aux::time_now32(); + + // if we're a seed, this is the timestamp of when we became one + time_point32 m_became_seed = aux::time_now32(); + + // if we're finished, this is the 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::int64_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; + aux::handler_storage m_deferred_handler_storage; + + // 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 + // ============================== + + // if we're currently in upload-mode this is the time timestamp of when + // we entered it + 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; + + // 8 bits free + +// ---- + + // the number of unchoked peers in this torrent + unsigned int m_num_uploads:24; + + // 4 unused bits + + // when this is true, this torrent supports peer exchange + bool m_enable_pex: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; + + // this is set to true if all piece layers were successfully loaded and + // validated. Only for v2 torrents + // TODO: this member can probably be removed + bool m_v2_piece_layers_validated:1; + +// ---- + + // 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)}; + + // user data as passed in by add_torrent_params + client_data_t m_userdata; + +// ---- + + // 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/docs/include/libtorrent/torrent_flags.hpp b/docs/include/libtorrent/torrent_flags.hpp new file mode 100644 index 0000000..21356d8 --- /dev/null +++ b/docs/include/libtorrent/torrent_flags.hpp @@ -0,0 +1,312 @@ +/* + +Copyright (c) 2017-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_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. + TORRENT_DEPRECATED constexpr torrent_flags_t 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. + TORRENT_DEPRECATED constexpr torrent_flags_t 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. + TORRENT_DEPRECATED constexpr torrent_flags_t 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. + TORRENT_DEPRECATED constexpr torrent_flags_t 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. + TORRENT_DEPRECATED constexpr torrent_flags_t 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; + + // if this flag is set, the resume data will be assumed to be correct + // without validating it against any files on disk. This may be used when + // restoring a session by loading resume data from disk. It will save time + // and also delay any hard disk errors until files are actually needed. If + // the resume data cannot be trusted, or if a torrent is added for the first + // time to some save path that may already have some of the files, this flag + // should not be set. + constexpr torrent_flags_t no_verify_files = 22_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/docs/include/libtorrent/torrent_handle.hpp b/docs/include/libtorrent/torrent_handle.hpp new file mode 100644 index 0000000..4640548 --- /dev/null +++ b/docs/include/libtorrent/torrent_handle.hpp @@ -0,0 +1,1359 @@ +/* + +Copyright (c) 2019, Amir Abrams +Copyright (c) 2003-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2015, 2018, Steven Siloti +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Falcosc +Copyright (c) 2017, 2020, AllSeeingEyeTolledEweSew +Copyright (c) 2019, ghbplayer +Copyright (c) 2019, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/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/torrent_info.hpp" +#include "libtorrent/peer_info.hpp" // for peer_source_flags_t +#include "libtorrent/download_priority.hpp" +#include "libtorrent/pex_flags.hpp" +#include "libtorrent/client_data.hpp" +#include "libtorrent/address.hpp" // for address_v4 and address_v6 + +namespace libtorrent { +namespace aux { + struct session_impl; +} + +#if TORRENT_ABI_VERSION == 1 + struct peer_list_entry; +#endif + struct torrent; + struct client_data_t; + +#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; + using file_progress_flags_t = flags::bitfield_flag; + + // 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); + tcp::endpoint peer() const; + + // 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 const* 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. + TORRENT_DEPRECATED state_t 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 struct 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. Using this + // flag when adding a piece that is actively being downloaded from other + // peers may have some unexpected consequences, as blocks currently + // being downloaded from peers may not be replaced. + 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()`` returns a vector with information about pieces + // that are partially downloaded or not downloaded but partially + // requested. See partial_piece_info for the fields in the returned + // vector. + std::vector get_download_queue() const; + 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 + +#if TORRENT_ABI_VERSION <= 2 + using file_progress_flags_t = libtorrent::file_progress_flags_t; +#endif + // 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. + static constexpr file_progress_flags_t piece_granularity = 0_bit; + + // This function fills in the supplied vector, or returns a 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 currently + // 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, file_progress_flags_t flags = {}) const; + std::vector file_progress(file_progress_flags_t flags = {}) 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&, client_data_t)> const& ext + , client_data_t userdata = client_data_t{}); + + // ``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 + // removed from the session AND destructed. + // + // To tell if the torrent_handle is in the session, use + // torrent_handle::in_session(). This will return true before + // session_handle::remove_torrent() is called, and false + // afterward. + // + // Clients should only use is_valid() to determine if the result of + // session::find_torrent() was successful. + // + // Unlike other member functions which return a value, is_valid() + // completes immediately, without blocking on a result from the + // network thread. Also unlike other functions, it never throws + // the system_error exception. + 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. + // + // For possible values of the ``flags`` parameter, see pause_flags_t. + // + // To know if a torrent is paused or not, call + // ``torrent_handle::flags()`` and check for the + // ``torrent_status::paused`` flag. + // + // .. 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) try + // { + // torrent_status s = h.status(); + // if (!s.has_metadata || !s.need_save_resume_data()) continue; + // + // h.save_resume_data(); + // ++outstanding_resume_data; + // } + // catch (lt::system_error const& e) + // { + // // the handle was invalid, ignore this one and move to the next + // } + // + // 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); + + // torrent_file() 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. + // + // Note that the torrent_info object returned here may be a different + // instance than the one added to the session, with different attributes + // like piece layers, dht nodes and trackers. A torrent_info object does + // not round-trip cleanly when added to a session. + // + // This means if you want to create a .torrent file by passing the + // torrent_info object into create_torrent, you need to use + // torrent_file_with_hashes() instead. + // + // torrent_file_with_hashes() returns a *copy* of the internal + // torrent_info and piece layer hashes (if it's a v2 torrent). The piece + // layers will only be included if they are available. If this torrent + // was added from a .torrent file with piece layers or if it's seeding, + // the piece layers are available. This function is more expensive than + // torrent_file() since it needs to make copies of this information. + // + // When constructing a create_torrent object from a torrent_info that's + // in a session, you need to use this function. + // + // Note that a torrent added from a magnet link may not have the full + // merkle trees for all files, and hence not have the complete piece + // layers. In that state, you cannot create a .torrent file even from + // the torrent_info returned from torrent_file_with_hashes(). Once the + // torrent completes downloading all files, becoming a seed, you can + // make a .torrent file from it. + std::shared_ptr torrent_file() const; + std::shared_ptr torrent_file_with_hashes() const; + + // returns the piece layers for all files in the torrent. If this is a + // v1 torrent (and doesn't have any piece layers) it returns an empty + // vector. This is a blocking call that will synchronize with the + // libtorrent network thread. + std::vector> piece_layers() 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 flags() instead + 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. + // To synchronize with the priorities taking effect, wait for the + // file_prio_alert. + // + // 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. + // + // Moving data from a file into the part file is currently not + // supported. If a file has its priority set to 0 *after* it has already + // been created, it will not be moved into the partfile. + 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. + // + // ``force_lsd_announce`` will announce the torrent on LSD + // immediately. + void force_reannounce(int seconds = 0, int idx = -1, reannounce_flags_t = {}) const; + void force_dht_announce() const; + void force_lsd_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. + // + // When copying files, sparse regions are not likely to be preserved. + // This makes it proportionally more expensive to move a large torrent + // when only few pieces have been downloaded, since the files are then + // allocated with zeros in the destination directory. + 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 + // 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 + + // returns the info-hash(es) 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. + // The ``info_hash()`` returns the SHA-1 info-hash for v1 torrents and a + // truncated hash for v2 torrents. For the full v2 info-hash, use + // ``info_hashes()`` instead. + sha1_hash info_hash() const; + info_hash_t info_hashes() 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; + + // returns the userdata pointer as set in add_torrent_params + client_data_t userdata() const; + + // Returns true if the torrent is in the session. It returns true before + // session::remove_torrent() is called, and false afterward. + // + // Note that this is a blocking function, unlike torrent_handle::is_valid() + // which returns immediately. + bool in_session() 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/docs/include/libtorrent/torrent_info.hpp b/docs/include/libtorrent/torrent_info.hpp new file mode 100644 index 0000000..3d63d6a --- /dev/null +++ b/docs/include/libtorrent/torrent_info.hpp @@ -0,0 +1,775 @@ +/* + +Copyright (c) 2003-2011, 2013-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2016, Markus +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2017-2019, Steven Siloti +Copyright (c) 2017, 2019, Andrei Kurushin +Copyright (c) 2019, Amir Abrams +Copyright (c) 2020, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/info_hash.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/aux_/merkle_tree.hpp" + +namespace libtorrent { + + struct invariant_access; + +namespace aux { + + // 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 url_, type_t type_ + , std::string auth_ = std::string() + , headers_t 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; + }; + + using torrent_info_flags_t = flags::bitfield_flag; + +TORRENT_VERSION_NAMESPACE_3 + + // 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(info_hash_t 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) {} +#endif // TORRENT_ABI_VERSION + + // frees all storage associated with this torrent_info object + ~torrent_info(); + + // hidden + torrent_info& operator=(torrent_info const&) = delete; + torrent_info& operator=(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; + + // 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); + + // .. warning:: + // Using `remap_files()` is discouraged as it's incompatible with v2 + // torrents. This is because the piece boundaries and piece hashes in + // v2 torrents are intimately tied to the file boundaries. Instead, + // just rename individual files, or implement a custom disk_interface + // to customize how to store files. + // + // 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. + // + // ``trackers()`` returns all entries from announce-list. + // + // ``clear_trackers()`` removes all trackers from announce-list. + 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; } + void clear_trackers(); + + // 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. + 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; +#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. + // + // ``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& ext_auth = std::string() + , web_seed_entry::headers_t const& ext_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()`` returns the total number of bytes the torrent-file + // represents. Note that this is the number of pieces times the piece + // size (modulo the last piece possibly being smaller). With pad files, + // the total size will be larger than the sum of all (regular) file + // sizes. + std::int64_t total_size() const { return m_files.total_size(); } + + // ``piece_length()`` and ``num_pieces()`` returns 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. + 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. For BitTorrent v2 support, use + // ``info_hashes()`` to get an object that may hold both a v1 and v2 + // info-hash + sha1_hash info_hash() const noexcept; + info_hash_t const& info_hashes() const { return m_info_hash; } + + // returns whether this torrent has v1 and/or v2 metadata, respectively. + // Hybrid torrents have both. These are shortcuts for + // info_hashes().has_v1() and info_hashes().has_v2() calls. + bool v1() const; + bool v2() const; + +#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); } + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + TORRENT_DEPRECATED + file_entry file_at(int index) const { return m_files.at_deprecated(index); } + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#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 bool(m_flags & private_torrent); } + + // 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 bool(m_flags & i2p); } + + // internal + bool v2_piece_hashes_verified() const { return bool(m_flags & v2_has_piece_hashes); } + void set_piece_layers(aux::vector, file_index_t> pl); + + // 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_PRECOND(index >= piece_index_t(0)); + TORRENT_ASSERT_PRECOND(index < m_files.end_piece()); + TORRENT_ASSERT(is_loaded()); + int const idx = static_cast(index); + TORRENT_ASSERT(m_piece_hashes > 0); + TORRENT_ASSERT(m_piece_hashes < m_info_section_size); + TORRENT_ASSERT(idx < int((m_info_section_size - m_piece_hashes) / 20)); + return &m_info_section[std::ptrdiff_t(m_piece_hashes) + idx * 20]; + } + + bool is_loaded() const { return m_files.num_files() > 0; } + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // ``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. + TORRENT_DEPRECATED + std::vector const& merkle_tree() const { return m_merkle_tree; } + TORRENT_DEPRECATED + void set_merkle_tree(std::vector& h) + { TORRENT_ASSERT(h.size() == m_merkle_tree.size() ); m_merkle_tree.swap(h); } +#endif + + // ``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, 0 is + // returned. + // .. _`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 `max_pieces` parameter allows limiting the amount of memory + // dedicated to loading the torrent, and fails for torrents that exceed + // the limit. To load large torrents, this limit may also need to be + // raised in settings_pack::max_piece_count and in calls to + // read_resume_data(). + bool parse_info_section(bdecode_node const& info, error_code& ec, int max_pieces); + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED + bool parse_info_section(bdecode_node const& info, error_code& ec); +#endif + + // 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; + + // returns a the raw info section of the torrent file. + // The underlying buffer is still owned by the torrent_info object + span info_section() const + { return span(m_info_section.get(), m_info_section_size); } + +#if TORRENT_ABI_VERSION <= 2 + // swap the content of this and ``ti``. + TORRENT_DEPRECATED + 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()``. + // Even though the bytes returned by ``metadata()`` are not ``const``, + // they must not be modified. + TORRENT_DEPRECATED + int metadata_size() const { return m_info_section_size; } + TORRENT_DEPRECATED + boost::shared_array metadata() const; +#endif + + // return the bytes of the piece layer hashes for the specified file. If + // the file doesn't have a piece layer, an empty span is returned. + // The span size is divisible by 32, the size of a SHA-256 hash. + // If the size of the file is smaller than or equal to the piece size, + // the files "root hash" is the hash of the file and is not saved + // separately in the "piece layers" field, but this function still + // returns the root hash of the file in that case. + span piece_layer(file_index_t) const; + + // clears the piece layers from the torrent_info. This is done by the + // session when a torrent is added, to avoid storing it twice. The piece + // layer (or other hashes part of the merkle tree) are stored in the + // internal torrent object. + void free_piece_layers(); + + // internal + void internal_set_creator(string_view); + void internal_set_creation_date(std::time_t); + void internal_set_comment(string_view); + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // internal + TORRENT_DEPRECATED + bool add_merkle_nodes(std::map const& + , piece_index_t) { return false; } + TORRENT_DEPRECATED + std::map build_merkle_list(piece_index_t) const + { + return std::map(); + } + + // returns whether or not this is a merkle torrent. + // see `BEP 30`__. + // + // __ https://www.bittorrent.org/beps/bep_0030.html + TORRENT_DEPRECATED + bool is_merkle_torrent() const { return !m_merkle_tree.empty(); } +#endif + + private: + + // populate the piece layers from the metadata + bool parse_piece_layers(bdecode_node const& e, error_code& ec); + + bool parse_torrent_file(bdecode_node const& torrent_file, 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 struct ::lt::invariant_access; + void check_invariant() const; +#endif + + 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. These are offsets + // into the info dict 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 + // first value is the offset into the metadata where the string is, the + // second value 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 TORRENT_ABI_VERSION <= 2 + // 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; +#endif + + // v2 merkle tree for each file + // the actual hash buffers are always divisible by 32 (sha256_hash::size()) + aux::vector, file_index_t> m_piece_layers; + + // 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 + // it is used as if immutable, it cannot be const for technical reasons + // right now. + boost::shared_array m_info_section; + + // 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(es) that identify this torrent + info_hash_t m_info_hash; + + // this is the offset into the m_info_section buffer to the first byte of + // the first SHA-1 hash + std::int32_t m_piece_hashes = 0; + + // the number of bytes in m_info_section + std::int32_t m_info_section_size = 0; + + // 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. + static constexpr torrent_info_flags_t multifile = 0_bit; + + // this is true if the torrent is private. i.e., is should not + // be announced on the dht + static constexpr torrent_info_flags_t private_torrent = 1_bit; + + // 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) + static constexpr torrent_info_flags_t i2p = 2_bit; + + // this flag is set if we found an ssl-cert field in the info + // dictionary + static constexpr torrent_info_flags_t ssl_torrent = 3_bit; + + // v2 piece hashes were loaded from the torrent file and verified + static constexpr torrent_info_flags_t v2_has_piece_hashes = 4_bit; + + torrent_info_flags_t m_flags{}; + }; + +TORRENT_VERSION_NAMESPACE_3_END + +} + +#endif // TORRENT_TORRENT_INFO_HPP_INCLUDED diff --git a/docs/include/libtorrent/torrent_peer.hpp b/docs/include/libtorrent/torrent_peer.hpp new file mode 100644 index 0000000..aae9680 --- /dev/null +++ b/docs/include/libtorrent/torrent_peer.hpp @@ -0,0 +1,287 @@ +/* + +Copyright (c) 2014-2017, 2019, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2018, 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_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/info_hash.hpp" +#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 + + protocol_version protocol() { return protocol_v2 ? protocol_version::V2 : protocol_version::V1; } + + // 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. + std::uint32_t 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 + std::uint32_t optimistically_unchoked:1; + + // this is true if the torrent_peer is a seed, and we know for sure + // because we have connected to it and it told us it was a seed + std::uint32_t seed:1; + + // we've been told that this peer is upload-only, but we don't know for + // sure because we haven't connected to it yet. If we are finished, we + // will de-prioritize peers that may be seeds + std::uint32_t maybe_upload_only: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; + // this peer supports protocol version 2 + bool protocol_v2:1; +#if TORRENT_USE_ASSERTS + bool in_use = true; +#endif + }; + + struct TORRENT_EXTRA_EXPORT ipv4_peer : torrent_peer + { + ipv4_peer(tcp::endpoint const& ep, 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 dest, 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& ep, 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/docs/include/libtorrent/torrent_peer_allocator.hpp b/docs/include/libtorrent/torrent_peer_allocator.hpp new file mode 100644 index 0000000..53cc35b --- /dev/null +++ b/docs/include/libtorrent/torrent_peer_allocator.hpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2010, 2013-2017, 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_PEER_ALLOCATOR_HPP_INCLUDED +#define TORRENT_PEER_ALLOCATOR_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/aux_/pool.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 + + aux::pool m_ipv4_peer_pool{sizeof(libtorrent::ipv4_peer), 500}; + aux::pool m_ipv6_peer_pool{sizeof(libtorrent::ipv6_peer), 500}; +#if TORRENT_USE_I2P + aux::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/docs/include/libtorrent/torrent_status.hpp b/docs/include/libtorrent/torrent_status.hpp new file mode 100644 index 0000000..aebd713 --- /dev/null +++ b/docs/include/libtorrent/torrent_status.hpp @@ -0,0 +1,608 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, AllSeeingEyeTolledEweSew +Copyright (c) 2018, 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_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 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" +#endif + +TORRENT_VERSION_NAMESPACE_3 + + // 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 +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#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. +#if TORRENT_ABI_VERSION == 1 + allocating TORRENT_DEPRECATED_ENUM, +#else + unused_enum_for_backwards_compatibility_allocating, +#endif + + // 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 + TORRENT_DEPRECATED std::string 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 metadata for the torrent + static constexpr file_index_t error_file_metadata{-4}; + + // 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. + TORRENT_DEPRECATED time_duration 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). + // Once a torrent becomes seed, any piece- and file priorities are + // forgotten and all bytes are considered "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. + // Once a torrent becomes seed, any piece- and file priorities are + // forgotten and all bytes are considered "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) + TORRENT_DEPRECATED int time_since_upload = 0; + TORRENT_DEPRECATED int 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. + TORRENT_DEPRECATED int active_time = 0; + TORRENT_DEPRECATED int finished_time = 0; + TORRENT_DEPRECATED int 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. + TORRENT_DEPRECATED int last_scrape = 0; + + // the priority of this torrent + TORRENT_DEPRECATED int 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. + TORRENT_DEPRECATED bool 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. + TORRENT_DEPRECATED bool upload_mode = false; + + // true if the torrent is currently in share-mode, i.e. not downloading + // the torrent, but just helping the swarm out. + TORRENT_DEPRECATED bool share_mode = false; + + // true if the torrent is in super seeding mode + TORRENT_DEPRECATED bool 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()``. + TORRENT_DEPRECATED bool 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_ + TORRENT_DEPRECATED bool auto_managed = false; + + // true when the torrent is in sequential download mode. In this mode + // pieces are downloaded in order rather than rarest first. + TORRENT_DEPRECATED bool 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. + TORRENT_DEPRECATED bool 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. + TORRENT_DEPRECATED bool 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(). + TORRENT_DEPRECATED bool stop_when_ready = false; +#endif + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + + // the info-hash for this torrent + info_hash_t info_hashes; + + // 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_3_END +} // namespace libtorrent + +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/docs/include/libtorrent/tracker_manager.hpp b/docs/include/libtorrent/tracker_manager.hpp new file mode 100644 index 0000000..d077a80 --- /dev/null +++ b/docs/include/libtorrent/tracker_manager.hpp @@ -0,0 +1,412 @@ +/* + +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2004-2020, Arvid Norberg +Copyright (c) 2016-2017, Steven Siloti +Copyright (c) 2016, 2020, Alden Torres +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 + +#include "libtorrent/flags.hpp" +#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_context.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" +#include "libtorrent/ssl.hpp" + +namespace libtorrent { + + class tracker_manager; + struct timeout_handler; + class udp_tracker_connection; + class http_tracker_connection; + struct counters; +#if TORRENT_USE_I2P + class i2p_connection; +#endif +namespace aux { + struct session_logger; + struct session_settings; + struct resolver_interface; +} + +using tracker_request_flags_t = flags::bitfield_flag; + +// Kinds of tracker announces. This is typically indicated as the ``&event=`` +// HTTP query string parameter to HTTP trackers. +enum class event_t : std::uint8_t +{ + none, + completed, + started, + stopped, + paused +}; + + struct TORRENT_EXTRA_EXPORT tracker_request + { + tracker_request() = default; + + static constexpr tracker_request_flags_t scrape_request = 0_bit; + + // affects interpretation of peers string in HTTP response + // see parse_tracker_response() + static constexpr tracker_request_flags_t i2p = 1_bit; + + std::string url; + std::string trackerid; +#if TORRENT_ABI_VERSION == 1 + std::string auth; +#endif + + std::shared_ptr filter; + + std::int64_t downloaded = -1; + std::int64_t uploaded = -1; + std::int64_t left = -1; + std::int64_t corrupt = 0; + std::int64_t redundant = 0; + std::uint16_t listen_port = 0; + event_t event = event_t::none; + tracker_request_flags_t kind = {}; + + std::uint32_t key = 0; + int num_want = 0; + 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 = false; + + // this is set to true if this request was triggered by a "manual" call to + // scrape_tracker() or force_reannounce() + bool triggered_manually = false; + +#if TORRENT_USE_SSL + 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 + , operation_t op + , 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_context&); + + 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(); + + auto get_executor() { return m_timeout.get_executor(); } + + 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 req + , io_context& 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, operation_t op, 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, operation_t op, 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 send_fun + , send_fun_hostname_t send_fun_hostname + , counters& stats_counters + , aux::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_context& ios + , tracker_request&& r + , aux::session_settings const& sett + , std::weak_ptr c + = std::weak_ptr()); + void queue_request( + io_context& 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 stop(); + + 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; } + aux::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; + aux::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/docs/include/libtorrent/truncate.hpp b/docs/include/libtorrent/truncate.hpp new file mode 100644 index 0000000..0786c81 --- /dev/null +++ b/docs/include/libtorrent/truncate.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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_TRUNCATE_HPP_INCLUDED +#define TORRENT_TRUNCATE_HPP_INCLUDED + +#include "libtorrent/fwd.hpp" +#include "libtorrent/error_code.hpp" +#include + +namespace libtorrent { + +// Truncates files larger than specified in the file_storage, saved under +// the specified save_path. +TORRENT_EXPORT void truncate_files(file_storage const& fs, std::string const& save_path, storage_error& ec); + +} + +#endif diff --git a/docs/include/libtorrent/udp_socket.hpp b/docs/include/libtorrent/udp_socket.hpp new file mode 100644 index 0000000..db145c3 --- /dev/null +++ b/docs/include/libtorrent/udp_socket.hpp @@ -0,0 +1,173 @@ +/* + +Copyright (c) 2007-2020, Arvid Norberg +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016, 2020, 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_UDP_SOCKET_HPP_INCLUDED +#define TORRENT_UDP_SOCKET_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/io_context.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 "libtorrent/aux_/resolver_interface.hpp" + +#include +#include + +namespace libtorrent { + +namespace aux { struct alert_manager; } + struct socks5; + + using udp_send_flags_t = flags::bitfield_flag; + + class TORRENT_EXTRA_EXPORT udp_socket : single_threaded + { + public: + udp_socket(io_context& ios, aux::listen_socket_handle ls); + + // non-copyable + udp_socket(udp_socket const&) = delete; + udp_socket& operator=(udp_socket const&) = delete; + + 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; } + udp::socket::executor_type get_executor() { return m_socket.get_executor(); } + + template + void async_read(Handler&& h) + { + m_socket.async_wait(udp::socket::wait_read, std::forward(h)); + } + + template + void async_write(Handler&& h) + { + m_socket.async_wait(udp::socket::wait_write, 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, aux::alert_manager& alerts + , aux::resolver_interface& resolver, bool send_local_ep); + 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: + + 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; + + io_context& m_ioc; + + 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; + }; +} + +#endif diff --git a/docs/include/libtorrent/udp_tracker_connection.hpp b/docs/include/libtorrent/udp_tracker_connection.hpp new file mode 100644 index 0000000..90c950f --- /dev/null +++ b/docs/include/libtorrent/udp_tracker_connection.hpp @@ -0,0 +1,133 @@ +/* + +Copyright (c) 2004-2008, 2010, 2012, 2014-2017, 2019-2020, Arvid Norberg +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_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_context& 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 + , operation_t op + , 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/docs/include/libtorrent/union_endpoint.hpp b/docs/include/libtorrent/union_endpoint.hpp new file mode 100644 index 0000000..2aa2ec6 --- /dev/null +++ b/docs/include/libtorrent/union_endpoint.hpp @@ -0,0 +1,115 @@ +/* + +Copyright (c) 2010, 2013, 2015-2019, Arvid Norberg +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. + +*/ + +#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/docs/include/libtorrent/units.hpp b/docs/include/libtorrent/units.hpp new file mode 100644 index 0000000..6dd8cd5 --- /dev/null +++ b/docs/include/libtorrent/units.hpp @@ -0,0 +1,186 @@ +/* + +Copyright (c) 2016-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2019, Steven Siloti +Copyright (c) 2020, Silver Zachara +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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; + +#if TORRENT_USE_IOSTREAM + template + struct type_to_print_as + { + using type = typename std::conditional::type; + }; +#endif + + + 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; + +#if TORRENT_USE_IOSTREAM + friend std::ostream& operator<<(std::ostream& os, strong_typedef val) + { return os << static_cast::type>(static_cast(val)); } +#endif + + 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;} + +} // 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. Any specific torrent + // file has a well defined and immutable file list, and a file index into it + // always refers to the same file. + 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/docs/include/libtorrent/upnp.hpp b/docs/include/libtorrent/upnp.hpp new file mode 100644 index 0000000..b0c6a76 --- /dev/null +++ b/docs/include/libtorrent/upnp.hpp @@ -0,0 +1,388 @@ +/* + +Copyright (c) 2007-2010, 2013-2020, Arvid Norberg +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016-2017, 2020, Alden Torres +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/aux_/resolver.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/aux_/portmap.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/ssl.hpp" + +#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); +} // namespace upnp_errors + + // 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_context& ios + , aux::session_settings const& settings + , aux::portmap_callback& cb + , address_v4 listen_address + , address_v4 netmask + , std::string listen_device + , aux::listen_socket_handle ls); + ~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* fmt, ...) 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&&) noexcept; + 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::noexcept_movable> 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; + aux::noexcept_movable
    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_context& m_io_service; + + aux::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; + +#if TORRENT_USE_SSL + ssl::context m_ssl_ctx; +#endif + + aux::listen_socket_handle m_listen_handle; +}; + +} // namespace libtorrent + +namespace boost { +namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + +} +} + +#endif diff --git a/docs/include/libtorrent/utf8.hpp b/docs/include/libtorrent/utf8.hpp new file mode 100644 index 0000000..9c0596e --- /dev/null +++ b/docs/include/libtorrent/utf8.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2005, 2008-2009, 2013, 2016-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_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/docs/include/libtorrent/vector_utils.hpp b/docs/include/libtorrent/vector_utils.hpp new file mode 100644 index 0000000..01fc3bf --- /dev/null +++ b/docs/include/libtorrent/vector_utils.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2010, 2013-2014, 2016, 2018-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_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/docs/include/libtorrent/version.hpp b/docs/include/libtorrent/version.hpp new file mode 100644 index 0000000..8f26104 --- /dev/null +++ b/docs/include/libtorrent/version.hpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2004, 2006, 2010, 2012, 2015, 2017-2020, Arvid Norberg +Copyright (c) 2016, Jan Berkel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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" +#include + +#define LIBTORRENT_VERSION_MAJOR 2 +#define LIBTORRENT_VERSION_MINOR 0 +#define LIBTORRENT_VERSION_TINY 6 + +// 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 "2.0.6.0" +#define LIBTORRENT_REVISION "7cf79a8d1" + +namespace libtorrent { + + // the major, minor and tiny versions of libtorrent + constexpr int version_major = 2; + constexpr int version_minor = 0; + constexpr int version_tiny = 6; + + // the libtorrent version in string form + constexpr char const* version_str = "2.0.6.0"; + + // the git commit of this libtorrent version + constexpr std::uint64_t version_revision = 0x7cf79a8d1; + + // returns the libtorrent version as string form in this format: + // "..." + TORRENT_EXPORT char const* version(); + +} + +namespace lt = libtorrent; + +#endif diff --git a/docs/include/libtorrent/web_connection_base.hpp b/docs/include/libtorrent/web_connection_base.hpp new file mode 100644 index 0000000..99eb566 --- /dev/null +++ b/docs/include/libtorrent/web_connection_base.hpp @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2010, 2012, 2014-2020, Arvid Norberg +Copyright (c) 2016, 2019, 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 WEB_CONNECTION_BASE_HPP_INCLUDED +#define WEB_CONNECTION_BASE_HPP_INCLUDED + +#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_EXTRA_EXPORT web_connection_base + : public peer_connection + { + friend struct 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& pack + , web_seed_t const& 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/docs/include/libtorrent/web_peer_connection.hpp b/docs/include/libtorrent/web_peer_connection.hpp new file mode 100644 index 0000000..b6973ff --- /dev/null +++ b/docs/include/libtorrent/web_peer_connection.hpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2006-2007, 2009-2020, Arvid Norberg +Copyright (c) 2016-2017, 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_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 struct 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& 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/docs/include/libtorrent/write_resume_data.hpp b/docs/include/libtorrent/write_resume_data.hpp new file mode 100644 index 0000000..832bff9 --- /dev/null +++ b/docs/include/libtorrent/write_resume_data.hpp @@ -0,0 +1,58 @@ +/* + +Copyright (c) 2017-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_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); + + // writes only the fields to create a .torrent file. This function may fail + // with a ``std::system_error`` exception if: + // + // * The add_torrent_params object passed to this function does not contain the + // info dictionary (the ``ti`` field) + // * The piece layers are not complete for all files that need them + TORRENT_EXPORT entry write_torrent_file(add_torrent_params const& atp); +} + +#endif diff --git a/docs/include/libtorrent/xml_parse.hpp b/docs/include/libtorrent/xml_parse.hpp new file mode 100644 index 0000000..014dc01 --- /dev/null +++ b/docs/include/libtorrent/xml_parse.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2007, 2011-2017, 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_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/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..9e78b8b --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,225 @@ +.. raw:: html + +
    + +Getting started + +* download_ +* building_ +* tutorial_ +* overview_ +* examples_ +* features_ + +-------- + +Documentation + +* reference_ +* `blog`_ +* `upgrade to 2.0`_ +* `upgrade to 1.2`_ +* contributing_ +* troubleshooting_ +* tuning_ +* fuzzing_ +* `security audit (2020)`_ +* `projects using libtorrent`_ + +-------- + +Contact + +* `mailing list`_ (archive_) +* `report bugs`_ +* `github page`_ + +-------- + +Extensions + +* uTP_ +* `extensions protocol`_ +* `libtorrent plugins`_ +* `streaming`_ +* `DHT extensions`_ +* `DHT security extension`_ +* `DHT store extension`_ +* `UDP tracker protocol`_ +* `HTTP seed`_ +* multi-tracker_ + +-------- + +Bindings + +* python_ +* Java_ +* golang_ +* node_ + +-------- + +* `Introduction, slides`_ + +.. raw:: html + +
    +
    + +.. _download: https://github.com/arvidn/libtorrent/releases +.. _features: features-ref.html +.. _tutorial: tutorial-ref.html +.. _contributing: contributing.html +.. _building: building.html +.. _examples: examples.html +.. _overview: manual-ref.html +.. _reference: reference.html +.. _`upgrade to 2.0`: upgrade_to_2.0-ref.html +.. _`upgrade to 1.2`: upgrade_to_1.2-ref.html +.. _troubleshooting: troubleshooting.html +.. _tuning: tuning-ref.html +.. _fuzzing: fuzzing.html +.. _`security audit (2020)`: security-audit.html +.. _uTP: utp.html +.. _`extensions protocol`: extension_protocol.html +.. _`libtorrent plugins`: 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 +.. _golang: https://github.com/steeve/libtorrent-go +.. _node: https://github.com/fanatid/node-libtorrent + +.. _`Introduction, slides`: bittorrent.pdf + +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. + +__ client_test.html + +.. image:: img/screenshot_thumb.png + :target: client_test.html + :alt: screenshot of libtorrent's client_test + :class: front-page-screenshot + :width: 400 + :height: 239 + +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 + +.. raw:: html + +
    + + bitcoin address for libtorrent donations + +contribute +========== + +If your organization uses libtorrent, please consider supporting its development. +See the contributing_ page for other ways to help out. + +.. raw:: html + +
    + + +
    + + + + + + + +
    +
    + + + +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/makefile b/docs/makefile new file mode 100644 index 0000000..2942584 --- /dev/null +++ b/docs/makefile @@ -0,0 +1,231 @@ +# this makefile assumes that you have docutils and rst2pdf installed +# (python-docutils) as well as aafigure (python-aafigure) + +ifndef WEB_PATH +WEB_PATH = ~/web/products/libtorrent +endif + +ifndef RST2HTML +RST2HTML:=rst2html +endif + +ifndef AAFIGURE +AAFIGURE=aafigure +endif + +REFERENCE_TARGETS = \ + manual-ref \ + tutorial-ref \ + tuning-ref \ + features-ref \ + upgrade_to_1.2-ref \ + upgrade_to_2.0-ref \ + reference \ + reference-Core \ + reference-DHT \ + reference-Session \ + reference-Torrent_Handle \ + reference-Torrent_Info \ + reference-Trackers \ + reference-PeerClass \ + reference-Torrent_Status \ + reference-Stats \ + reference-Resume_Data \ + reference-Add_Torrent \ + reference-Plugins \ + reference-Create_Torrents \ + reference-Error_Codes \ + reference-Storage \ + reference-Custom_Storage \ + reference-Utility \ + reference-Bencoding \ + reference-Alerts \ + reference-Filter \ + reference-Settings \ + reference-Bdecoding \ + reference-ed25519 + +MANUAL_TARGETS = index \ + udp_tracker_protocol \ + dht_rss \ + dht_store \ + client_test \ + building \ + troubleshooting \ + contributing\ + examples \ + extension_protocol \ + dht_extensions \ + dht_sec \ + python_binding \ + projects \ + utp \ + hacking \ + streaming \ + fuzzing \ + security-audit + +TARGETS = single-page-ref \ + $(MANUAL_TARGETS) \ + $(REFERENCE_TARGETS) + +FIGURES = $(addprefix img/, \ + read_disk_buffers \ + write_disk_buffers \ + hacking \ + utp_stack \ + storage \ + troubleshooting \ + troubleshooting_thumb \ + screenshot_thumb \ + logo-color \ + logo-bw \ + our_delay_base_thumb \ + delays_thumb \ + cwnd_thumb \ +) + +STAGE_IMG = $(addprefix img/, \ + logo-color-text \ + logo-color \ + screenshot \ + bitcoin \ + ip_id_v4 \ + ip_id_v6 \ + hash_distribution \ + complete_bit_prefixes \ + our_delay_base \ + delays \ + cwnd \ +) + +ORIG_HEADERS = $(wildcard ../include/libtorrent/*.hpp) +HEADERS = $(ORIG_HEADERS:../%=%) + +html: $(TARGETS:=.html) $(FIGURES:=.png) todo.html favicon.ico $(HEADERS) + +stage: $(addprefix $(WEB_PATH)/, $(TARGETS:=.html) $(FIGURES:=.png) todo.html favicon.ico style.css $(HEADERS) $(STAGE_IMG:=.png)) + +rst: $(TARGETS:=.rst) todo.html + +pdf: $(TARGETS:=.pdf) $(FIGURES:=.png) + +epub: $(TARGETS:=.epub) $(FIGURES:=.png) + +all: html pdf favicon.ico + +img/logo-color-text.png: img/logo-color-text.svg + convert -background transparent $< -resize 400 $@ + +img/%.png: img/%.svg + convert -background transparent $< -resize 128x128 $@ + +favicon.ico: favicon-16.png favicon-32.png favicon-64.png + icotool -o $@ -c $? + +favicon-16.png: img/logo-color.svg + convert -background transparent $< -resize 16x16 $@ + +favicon-32.png: img/logo-color.svg + convert -background transparent $< -resize 32x32 $@ + +favicon-64.png: img/logo-color.svg + convert -background transparent $< -resize 64x64 $@ + +single-page-ref.rst: gen_reference_doc.py ../include/libtorrent/*.hpp ../include/libtorrent/kademlia/*.hpp settings-ref.rst stats_counters.rst + python3 gen_reference_doc.py --single-page + +settings.rst hunspell/settings.dic: ../include/libtorrent/settings_pack.hpp hunspell/libtorrent.dic + python3 gen_settings_doc.py || { rm $@; exit 1; } + cat hunspell/libtorrent.dic >>hunspell/settings.dic + +stats_counters.rst: ../src/session_stats.cpp ../include/libtorrent/performance_counters.hpp + python3 gen_stats_doc.py || { rm $@; exit 1; } + +manual.rst: stats_counters.rst + touch manual.rst + +%_thumb.png: %.png + convert $< -resize 400 $@ + +img/troubleshooting_thumb.png: img/troubleshooting.png + convert $< -resize 800x800 $@ + +todo.html:gen_todo.py ../src/*.cpp ../include/libtorrent/*.hpp + python3 gen_todo.py + +$(REFERENCE_TARGETS:=.rst) plain_text_out.txt settings-ref.rst:gen_reference_doc.py ../include/libtorrent/*.hpp ../include/libtorrent/kademlia/*.hpp manual.rst tuning.rst tutorial.rst features.rst settings.rst stats_counters.rst hunspell/settings.dic + python3 gen_reference_doc.py --plain-output + +spell-check:plain_text_out.txt $(MANUAL_TARGETS:=.html) manual.rst settings.rst tutorial.rst + python3 filter-rst.py manual.rst >manual-plain.txt + python3 filter-rst.py tutorial.rst >tutorial-plain.txt + python3 filter-rst.py tuning.rst >tuning-plain.txt + python3 filter-rst.py settings.rst >settings-plain.txt + python3 filter-rst.py upgrade_to_1.2.rst >upgrade-1_2-plain.txt + python3 filter-rst.py upgrade_to_2.0.rst >upgrade-2_0-plain.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/libtorrent.dic -l plain_text_out.txt >hunspell-report.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/libtorrent.dic -l manual-plain.txt >>hunspell-report.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/libtorrent.dic -l tutorial-plain.txt >>hunspell-report.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/libtorrent.dic -l tuning-plain.txt >>hunspell-report.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/libtorrent.dic -l upgrade-1_2-plain.txt >>hunspell-report.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/libtorrent.dic -l upgrade-2_0-plain.txt >>hunspell-report.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/settings.dic -l settings-plain.txt >>hunspell-report.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/libtorrent.dic -H -l $(MANUAL_TARGETS:=.html) >>hunspell-report.txt + @if [ -s hunspell-report.txt ]; then echo 'spellcheck failed, fix words or add to dictionary:'; cat hunspell-report.txt; false; fi; + +%.epub:%.rst + rst2epub --exit-status=2 $< $@ + +%.pdf:%.rst + rst2pdf $< -o $@ --stylesheets stylesheet + +%.html:%.rst template.txt template2.txt + $(RST2HTML) --title=libtorrent --exit-status=2 --template=template.txt --stylesheet-path=style.css --link-stylesheet --no-toc-backlinks $< > $@ || { rm $@; exit 1; } + +%.png:%.dot + dot -Tpng $< >$@ || { rm $@; exit 1; } + +%.png:%.diagram + $(AAFIGURE) --scale 0.6 -o $@ $< || { rm $@; exit 1; } + +include/libtorrent/%.hpp: ../include/libtorrent/%.hpp + mkdir -p include/libtorrent >/dev/null + cp $< $@ + +# stage rules + +$(WEB_PATH)/%.html:%.rst template.txt template2.txt + mkdir -p $(WEB_PATH) >/dev/null + $(RST2HTML) --title=libtorrent --exit-status=2 --template=template2.txt --stylesheet-path=style.css --link-stylesheet --no-toc-backlinks $< > $@ || { rm $@; exit 1; } + +$(WEB_PATH)/img/%.png: img/%.png + mkdir -p $(WEB_PATH)/img >/dev/null + cp $< $@ + +$(WEB_PATH)/%.png: %.png + cp $< $@ + +$(WEB_PATH)/%.css: %.css + cp $< $@ + +$(WEB_PATH)/%.svg: %.svg + cp $< $@ + +$(WEB_PATH)/%.html: %.html + cp $< $@ + +$(WEB_PATH)/%.ico: %.ico + cp $< $@ + +$(WEB_PATH)/include/libtorrent/%.hpp: ../include/libtorrent/%.hpp + mkdir -p $(WEB_PATH)/include/libtorrent + cp $< $@ + +$(WEB_PATH)/img/%.png: img/%.png + mkdir -p $(WEB_PATH)/img >/dev/null + cp $< $@ + +clean: + rm -f $(TARGETS:=.html) $(TARGETS:=.pdf) $(FIGURES:=.png) $(REFERENCE_TARGETS:=.rst) settings.rst todo.html reference*.html stats_counters.rst hunspell/settings.dic favicon-16.png favicon-32.png favicon-64.png favicon.ico + diff --git a/docs/manual.rst b/docs/manual.rst new file mode 100644 index 0000000..c0d486b --- /dev/null +++ b/docs/manual.rst @@ -0,0 +1,1287 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :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, possibly passing in the state from a previous session. + use read_session_params() and pass in the resulting session_params object to + the session constructor. +* 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 session_state() and write_session_params()) +* 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-ref.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. + +.torrent file +------------- + +To save a .torrent file from a torrent that was added by magnet link (or added any way really): + +* call save_resume_data() on the torrent_handle, make sure to pass in the ``save_info_dict`` flag +* wait for resume_data_alert +* call write_torrent_file() passing in the add_torrent_params object from the alert. + +The resume data format is very similar to the .torrent file format, and when +including the info-dict in the resume data, the resume file can be used as a +.torrent file (with just a few minor exceptions). + +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. Pass in the ``save_info_dict`` flag. +* wait for resume_data_alert +* save the add_torrent_params object using write_resume_data() + +When adding a torrent using resume data, load it using read_resume_data(). This +populates an add_torrent_params object, which can be passed directly to +add_torrent() or async_add_torrent() on the session object. 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. | +| | This is a 20 byte SHA-1 hash of the info section of the | +| | torrent if this is a v1 or v1+v2-hybrid torrent. | ++--------------------------+--------------------------------------------------------------+ +| ``info-hash2`` | string, the v2 info hash of the torrent this data is saved. | +| | for, in case it is a v2 or v1+v2-hybrid torrent. This is a | +| | 32 byte SHA-256 hash of the info section of the torrent. | ++--------------------------+--------------------------------------------------------------+ +| ``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. | ++--------------------------+--------------------------------------------------------------+ +| ``trees`` | list. In case this is a v2 (or v1+v2-hybrid) torrent, this | +| | is an optional list containing the merkle tree nodes we know | +| | of so far, for all files. It's a list of dictionaries, one | +| | entry for each file in the torrent. The entries have the | +| | following structure: | +| | | +| | +--------------+-------------------------------------------+ | +| | | ``hashes`` | string. Sequence of 32 byte (SHA-256) | | +| | | | hashes, representing the nodes in the | | +| | | | merkle hash tree for this file. Some | | +| | | | hashes may be all zeros, if we haven't | | +| | | | downloaded them yet. | | +| | +--------------+-------------------------------------------+ | +| | | ``mask`` | string. When present, a bitmask (of ``0`` | | +| | | | and ``1`` characters, indicating which | | +| | | | hashes of the full tree are included in | | +| | | | the ``hashes`` key. This is used to avoid | | +| | | | storing large numbers of zeros. | | +| | +--------------+-------------------------------------------+ | +| | | ``verified`` | string. This indicates which leaf nodes | | +| | | | in the tree have been verified correct. | | +| | | | There is one character per leaf, ``0`` | | +| | | | means not verified, ``1`` means verified. | | +| | +--------------+-------------------------------------------+ | +| | | ++--------------------------+--------------------------------------------------------------+ +| ``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:: img/utp_stack.png + :class: bw + :align: right + +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 built with SSL torrent support +(``TORRENT_SSL_PEERS``) 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: + +.. 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/plain_text_out.txt b/docs/plain_text_out.txt new file mode 100644 index 0000000..f0b5044 --- /dev/null +++ b/docs/plain_text_out.txt @@ -0,0 +1,6569 @@ + +Not an 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 recursion depth limit exceeded + +bencoded item count limit exceeded + +integer overflow + +the number of error codes + +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. + +creates a default constructed node, it will have the type ``none_t``. + +For owning nodes, the copy will create a copy of the tree, but the +underlying buffer remains the same. + +uninitialized or default constructed. This is also used +to indicate that a node was not found in some cases. + +a dictionary node. The ``dict_find_`` functions are valid. + +a list node. The ``list_`` functions are valid. + +a string node, the ``string_`` functions are valid. + +an integer node. The ``int_`` functions are valid. + +the types of bdecoded nodes + +the type of this node. See type_t. + +returns true if type() != none_t. + +return a non-owning reference to this node. This is useful to refer to +the root node without copying it in assignments. + +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. +the ``data_offset()`` function returns the byte-offset to this node in, +starting from the beginning of the buffer that was parsed. + +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. + +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 ``string_view`` 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). + +``dict_at()`` returns the (key, value)-pair at the specified index in a +dictionary. Keys are only allowed to be strings. ``dict_at_node()`` also +returns the (key, value)-pair, but the key is returned as a +``bdecode_node`` (and it will always be a string). + +this function is only valid if ``type()`` == ``int_t``. It returns the +value of the integer. + +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_offset()`` returns the byte offset from the start of the parsed +bencoded buffer this string can be found. + +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. + +Swap contents. + +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(). + +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(). + +returns true if there is a non-fatal error in the bencoding of this node +or its children + +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. + +print the bencoded structure in a human-readable format to a string +that's returned. + +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. + +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); + + + + + + + + + + + + + + + + +See RFC 6887 Section 7.4 + + +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. + +saves settings (i.e. the settings_pack) + +saves dht state such as nodes and node-id, possibly accelerating +joining the DHT if provided at next session startup. + +load or save state from plugins + +load or save the IP filter set on the session + +returns the current session state. This can be passed to +write_session_params() to save the state to disk and restored using +read_session_params() when constructing a new session. The kind of +state that's included is all settings, the DHT routing table, possibly +plugin-specific state. +the flags parameter can be used to only save certain parts of the +session state + + 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. + +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. + +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. + +This will cause a dht_stats_alert to be posted. + +set the DHT state for the session. This will be taken into account the +next time the DHT is started, as if it had been passed in via the +session_params on startup. + +``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. + +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. + +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. + +``is_dht_running()`` returns true if the DHT support has been started +and false otherwise. + +``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. + +``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. + +query the DHT for an immutable item at the ``target`` hash. +the result is posted as a dht_immutable_item_alert. + +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. + +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. + +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. + +``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. + +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. + +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``. + +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. + +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. + +uTorrent peer exchange + Exchanges peers between clients. + +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. + + +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. + +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. + +built-in peer classes + +``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. + +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: + +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_. + +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_. + +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_. + +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_. + +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_. + +delete the files belonging to the torrent from disk. +including the part-file, if there is one + +delete just the part-file associated with this torrent + +when set, the session will start paused. Call +session_handle::resume() to start + +``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. + +remove_torrent() is non-blocking, but will remove the torrent from the +session synchronously. Calling session_handle::add_torrent() immediately +afterward with the same torrent will succeed. Note that this creates a +new handle which is not equal to the removed one. + +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``. Once the torrent is deleted, a +torrent_deleted_alert is posted. + +The torrent_handle remains valid for some time after remove_torrent() is +called. It will become invalid only after all libtorrent tasks (such as +I/O tasks) release their references to the torrent. Until this happens, +torrent_handle::is_valid() will return true, and other calls such +as torrent_handle::status() will succeed. Because of this, and because +remove_torrent() is non-blocking, the following sequence usually +succeeds (does not throw system_error): +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. + +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. + +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. + +protocols used by add_port_mapping() + +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. + +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. + +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. + +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. + +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. + + + + + +converts a setting integer (from the enums string_types, int_types or +bool_types) to a string, and vice versa. + +returns a settings_pack with every setting set to its default value + + + +the common interface to settings_pack and the internal representation of +settings. + + +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. + +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. + +clear the settings pack from all settings + +clear a specific setting from the pack + +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. + + + + + + +setting names (indices) are 16 bits. The two most significant +bits indicate what type the setting has. (string, int, bool) + + + + + +This is the traditional choker with a fixed number of unchoke +slots (as specified by settings_pack::unchoke_slots_limit). + +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`_. + + + +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. + +unchokes the peers we can send to the fastest. This might be a +bit more reliable in utilizing all available capacity. + +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. + + + + + + + +disables the mixed mode bandwidth balancing + +does not throttle uTP, throttles TCP to the same proportion +of throughput as there are TCP connections + + +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. + +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. + +only non-encrypted connections are allowed. + +the encoding policy options for use with +settings_pack::out_enc_policy and settings_pack::in_enc_policy. + +use only plain text encryption + +use only RC4 encryption + +allow both + +the encryption levels, to be used with +settings_pack::allowed_enc_level. + +No proxy server is used and all other fields are ignored. + +The server is assumed to be a `SOCKS4 server`_ that requires a +username. + + +The server is assumed to be a SOCKS5 server (`RFC 1928`_) that does +not require any authentication. The username and password are +ignored. + + +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. + + +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. + + +The server is assumed to be an HTTP proxy that requires user +authorization. The username and password will be sent to the proxy. + +route through a i2p SAM proxy + + +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. + + + +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. + +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. + +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. + +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. + +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_. + +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_. + +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. + +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(). + +sets the torrent into super seeding/initial seeding mode. If the torrent +is not a seed, this flag has no effect. + +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. + +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 + +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() + +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() + +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. + +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. + +set this flag to disable local service discovery for this torrent. + +set this flag to disable peer exchange for this torrent. + +if this flag is set, the resume data will be assumed to be correct +without validating it against any files on disk. This may be used when +restoring a session by loading resume data from disk. It will save time +and also delay any hard disk errors until files are actually needed. If +the resume data cannot be trusted, or if a torrent is added for the first +time to some save path that may already have some of the files, this flag +should not be set. + +all torrent flags combined. Can conveniently be used when creating masks +for flags + +if this tracker has returned an error or warning message +that message is stored here + +if this tracker failed the last time it was contacted +this error code specifies what error occurred + +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. + +the number of times in a row we have failed to announce to this +tracker. + +true while we're waiting for a response from the tracker. + +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. + +set to true when we send a event=completed. + + + +the local endpoint of the listen interface associated with this endpoint + +info_hashes[0] is the v1 info hash (SHA1) +info_hashes[1] is the v2 info hash (truncated SHA-256) + +set to false to not announce from this endpoint + +announces are sent to each tracker using every listen socket +this class holds information about one listen socket for one tracker + +constructs a tracker announce entry with ``u`` as the URL. + +tracker URL as it appeared in the torrent file + +the current ``&trackerid=`` argument passed to the tracker. +this is optional and is normally empty (in which case no +trackerid is sent). + +each local listen socket (endpoint) will announce to the tracker. This +list contains state per endpoint. + +the tier this tracker belongs to + +the max number of failures to announce to this tracker in +a row, before this tracker is not used anymore. 0 means unlimited + +the tracker was part of the .torrent file + +the tracker was added programmatically via the add_tracker() function + +the tracker was part of a magnet link + +the tracker was received from the swarm via tracker exchange + +flags for the source bitmask, each indicating where +we heard about this tracker + +a bitmask specifying which sources we got this tracker from. + +set to true the first time we receive a valid response +from this tracker. + +this class holds information about one bittorrent tracker, as it +relates to a specific torrent. + + +this is the same as default constructing followed by a call to +``update(data, len)``. + +append the following bytes to what is being hashed + +returns the SHA-1 digest of the buffers previously passed to +update() and the hasher constructor. + +restore the hasher state to be as if the hasher has just been +default constructed. + +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``. + + +this is the same as default constructing followed by a call to +``update(data, len)``. + +append the following bytes to what is being hashed + +returns the SHA-1 digest of the buffers previously passed to +update() and the hasher constructor. + +restore the hasher state to be as if the hasher has just been +default constructed. + + + +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. + +the constructor function for the default storage. On systems that support +memory mapped files (and a 64 bit address space) the memory mapped storage +will be constructed, otherwise the portable posix storage. + +default constructor, does not refer to any session +implementation object. + +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. + +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. + +The overloads taking ``flags`` can be used to start a session in +paused mode (by passing in ``session::paused``). Note that +``add_default_plugins`` do not have an affect on constructors that +take a session_params object. It already contains the plugins to use. + +Overload of the constructor that takes an external io_context to run +the session object on. This is primarily useful for tests that may want +to run multiple sessions on a single io_context, or low resource +systems where additional threads are expensive and sharing an +io_context with other events is fine. + + The session object does not cleanly terminate with an external + ``io_context``. The ``io_context::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_context, then + destruct the session_proxy object. + +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(). + +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:: + + struct session_proxy {}; + +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(). + +this function turns the resume data in an ``add_torrent_params`` object +into a bencoded structure + +writes only the fields to create a .torrent file. This function may fail +with a ``std::system_error`` exception if: + +* The add_torrent_params object passed to this function does not contain the + info dictionary (the ``ti`` field) +* The piece layers are not complete for all files that need them + + + + + + + + + + + + +SOCKS5 error values. If an error_code has the +socks error category (get_socks_category()), these +are the error values. + +returns the error_category for SOCKS5 errors + + +the interface for freeing disk buffers, used by the disk_buffer_holder. +when implementing disk_interface, this must also be implemented in order +to return disk buffers back to libtorrent + + + +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) + +default construct a holder that does not own any buffer + +frees disk buffer held by this object + +return a pointer to the held buffer, if any. Otherwise returns nullptr. + +free the held disk buffer, if any, and clear the holder. This sets the +holder object to a default-constructed state + +swap pointers of two disk buffer holders. + +if this returns true, the buffer may not be modified in place + +implicitly convertible to true if the object is currently holding a +buffer + + +The disk buffer holder acts like a ``unique_ptr`` that frees a disk buffer +when it's destructed + +If this buffer holder is moved-from, default constructed or reset, +``data()`` will return nullptr. + +compares if the torrent status objects come from the same torrent. i.e. +only the torrent_handle field is compared. + +a handle to the torrent whose status the object represents. + +The torrent has not started its download yet, and is +currently checking existing files. + +The torrent is trying to download metadata from peers. +This implies the ut_metadata extension is in use. + +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. + +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. + +In this state the torrent has finished downloading and +is a pure seeder. + +If the torrent was started in full allocation mode, this +indicates that the (disk) storage for the torrent is +allocated. + +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. + +the different overall states a torrent can be in + +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. + +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; + +special values for error_file to describe which file or component +encountered the error (``errc``). +the error did not occur on a file + +the error occurred setting up the SSL context + +the error occurred while loading the metadata for the torrent + +there was a serious error reported in this torrent. The error code +or a torrent log alert may provide more information. + +the error occurred with the partfile + +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``. + +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``. + +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``. + +the time until the torrent will announce itself to the tracker. + +the URL of the last working tracker. If no tracker request has +been successful yet, it's set to an empty string. + +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``. + +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. + +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. + +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. + +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. + +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. + +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``). + +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. + +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). +Once a torrent becomes seed, any piece- and file priorities are +forgotten and all bytes are considered "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. +Once a torrent becomes seed, any piece- and file priorities are +forgotten and all bytes are considered "wanted". + +are accumulated upload and download payload byte counters. They are +saved in and restored from resume data to keep totals across sessions. + +the posix-time when this torrent was added. i.e. what ``time(nullptr)`` +returned at the time. + +the posix-time when this torrent was finished. If the torrent is not +yet finished, this is 0. + +the time when we, or one of our peers, last saw a complete copy of +this torrent. + +The allocation mode for the torrent. See storage_mode_t for the +options. For more information, see storage-allocation_. + +a value in the range [0, 1], that represents the progress of the +torrent's current task. It may be checking files or downloading. + +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. + +the position this torrent has in the download +queue. If the torrent is a seed or finished, this is -1. + +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. + +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. + +the number of peers that are seeding that this client is +currently connected to. + +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(). + +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. + +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. + +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. + +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. + +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. + +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. + +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). + +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. + +the number of unchoked peers in this torrent. + +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``. + +the set limit of upload slots (unchoked peers) for this torrent. + +the set limit of number of connections for this torrent. + +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. + +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 + +the main state the torrent is in. See torrent_status::state_t. + +true if this torrent has unsaved changes +to its download state and statistics since the last resume data +was saved. + +true if all pieces have been downloaded. + +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. + +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). + +true if there has ever been an incoming connection attempt to this +torrent. + +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. + +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. + +the info-hash for this torrent + +the timestamps of the last time this torrent uploaded or downloaded +payload to any peer. + +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. + +reflects several of the torrent's flags. For more +information, see ``torrent_handle::flags()``. + +holds a snapshot of the status of a torrent, as queried by +torrent_handle::status(). + +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. + +The ``piece_limit`` parameter determines the largest number of pieces +allowed in the torrent that may be loaded as part of the resume data, if +it contains an ``info`` field. The overloads that take a flat buffer are +instead configured with limits on torrent sizes via load_torrent limits. + +In order to support large torrents, it may also be necessary to raise the +settings_pack::max_piece_count setting and pass a higher limit to calls +to torrent_info::parse_info_section(). + + + + + + +Kinds of tracker announces. This is typically indicated as the ``&event=`` +HTTP query string parameter to HTTP trackers. + +The index of the piece in which the range starts. + +The byte offset within that piece where the range starts. + +The size of the range, in bytes. + +returns true if the right hand side peer_request refers to the same +range as this does. + +represents a byte range within a piece. Internally this is is used for +incoming piece requests. + +the peer supports protocol encryption + +the peer is a seed + +the peer supports the uTP, transport protocol over UDP. + +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 + +protocol v2 +this is not a standard flag, it is only used internally + +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). + +copy bitfield from buffer ``b`` of ``bits`` number of bits, rounded up to +the nearest byte boundary. + +query bit at ``index``. Returns true if bit is 1, otherwise false. + +set bit at ``index`` to 0 (clear_bit) or 1 (set_bit). + +returns true if all bits in the bitfield are set + +returns true if no bit in the bitfield is set + +returns the size of the bitfield in bits. + +returns the number of 32 bit words are needed to represent all bits in +this bitfield. + +returns true if the bitfield has zero size. + +returns a pointer to the internal buffer of the bitfield, or +``nullptr`` if it's empty. + +swaps the bit-fields two variables refer to + +count the number of bits in the bitfield that are set to 1. + +returns the index of the first set bit in the bitfield, i.e. 1 bit. + +returns the index to the last cleared bit in the bitfield, i.e. 0 bit. + +The bitfield type stores any number of bits as a bitfield +in a heap allocated array. + + + + + + + + + + + +error values for the i2p_category error_category. + +returns the error category for I2P errors + +the major, minor and tiny versions of libtorrent + +the major, minor and tiny versions of libtorrent + +the major, minor and tiny versions of libtorrent + +the libtorrent version in string form + +the git commit of this libtorrent version + +returns the libtorrent version as string form in this format: +"..." + +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. + +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. + +Do not generate v1 metadata. The resulting torrent will only be usable by +clients which support v2. This requires setting all v2 hashes, with +set_hash2() before calling generate(). Setting v1 hashes (with +set_hash()) is an error with this flag set. + +do not generate v2 metadata or enforce v2 alignment and padding rules +this is mainly for tests, not recommended for production use. This +requires setting all v1 hashes, with set_hash(), before calling +generate(). Setting v2 hashes (with set_hash2()) is an error with +this flag set. + +This flag only affects v1-only torrents, and is only relevant +together with the v1_only_flag. This flag will force the +same file order and padding as a v2 (or hybrid) torrent would have. +It has the effect of ordering files and inserting pad files to align +them with piece boundaries. + +passing this flag to add_files() will ignore file attributes (such as +executable or hidden) when adding the files to the file storage. +Since not all filesystems and operating systems support all file +attributes the resulting torrent may differ depending on where it's +created. If it's important for torrents to be created consistently +across systems, this flag should be set. + +The ``piece_size`` is the size of each piece in bytes. It must be a +power of 2 and a minimum of 16 kiB. If a piece size of 0 is +specified, a piece_size will be set automatically. + +The file_storage (``fs``) parameter defines the files, sizes and +their properties for the torrent to be created. Set this up first, +before passing it to the create_torrent constructor. + +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 file_storage and torrent_info objects must stay alive for the + entire duration of the create_torrent object. + +The ``flags`` arguments specifies options for the torrent creation. It can +be any combination of the flags defined by create_flags_t. + +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. + +Whether the resulting torrent object is v1, v2 or hybrid depends on +whether any of the v1_only or v2_only flags were set on the +constructor. If neither were set, the resulting torrent depends on +which hashes were set. If both v1 and v2 hashes were set, a hybrid +torrent is created. + +Any failure will cause this function to throw system_error, with an +appropriate error message. These are the reasons this call may throw: + +* the file storage has 0 files +* the total size of the file storage is 0 bytes (i.e. it only has + empty files) +* not all v1 hashes (set_hash()) and not all v2 hashes (set_hash2()) + were set +* for v2 torrents, you may not have a directory with the same name as + a file. If that's encountered in the file storage, generate() + fails. + +returns an immutable reference to the file_storage used to create +the torrent from. + +Sets the comment for the torrent. The string ``str`` should be utf-8 encoded. +The comment in a torrent file is optional. + +Sets the creator of the torrent. The string ``str`` should be utf-8 encoded. +This is optional. + +sets the "creation time" field. Defaults to the system clock at the +time of construction of the create_torrent object. The timestamp is +specified in seconds, posix time. If the creation date is set to 0, +the "creation date" field will be omitted from the generated torrent. + +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(). +A SHA-1 hash of all zeros is internally used to indicate a hash that +has not been set. Setting such hash will not be considered set when +calling generate(). +This function will throw ``std::system_error`` if it is called on an +object constructed with the v2_only flag. + +sets the bittorrent v2 hash for file `file` of the piece `piece`. +`piece` is relative to the first piece of the file, starting at 0. The +first piece in the file can be computed with +file_storage::file_index_at_piece(). +The hash, `h`, is the root of the merkle tree formed by the piece's +16 kiB blocks. Note that piece sizes must be powers-of-2, so all +per-piece merkle trees are complete. +A SHA-256 hash of all zeros is internally used to indicate a hash +that has not been set. Setting such hash will not be considered set +when calling generate(). +This function will throw ``std::system_error`` if it is called on an +object constructed with the v1_only flag. + +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. + +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. + +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. + +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. + +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. + + +returns the number of pieces in the associated file_storage object. + +``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. + +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`_. + +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(). + +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: + +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. + +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: + +The overloads taking a settings_pack may be used to configure the +underlying disk access. Such as ``settings_pack::aio_threads``. + +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. + +called when the disk cache size has dropped +below the low watermark again and we can +resume downloading from peers + + +No error + +One of the arguments in the request is invalid + +The request failed + +The specified value does not exist in the array + +The source IP address cannot be wild-carded, but +must be fully specified + +The external port cannot be a wildcard, but must +be specified + +The port mapping entry specified conflicts with a +mapping assigned previously to another client + +Internal and external port value must be the same + +The NAT implementation only supports permanent +lease times on port mappings + +RemoteHost must be a wildcard and cannot be a +specific IP address or DNS name + +ExternalPort must be a wildcard and cannot be a +specific port + +error codes for the upnp_error_category. They hold error codes +returned by UPnP routers when mapping ports + +the boost.system error category for UPnP errors + +the error was unexpected and it is unknown which operation caused it + +this is used when the bittorrent logic +determines to disconnect + +a call to iocontrol failed + +a call to ``getpeername()`` failed (querying the remote IP of a +connection) + +a call to getname failed (querying the local IP of a +connection) + +an attempt to allocate a receive buffer failed + +an attempt to allocate a send buffer failed + +writing to a file failed + +reading from a file failed + +a non-read and non-write file operation failed + +a socket write operation failed + +a socket read operation failed + +a call to open(), to create a socket socket failed + +a call to bind() on a socket failed + +an attempt to query the number of bytes available to read from a socket +failed + +a call related to bittorrent protocol encryption failed + +an attempt to connect a socket failed + +establishing an SSL connection failed + +a connection failed to satisfy the bind interface setting + +a call to listen() on a socket + +a call to the ioctl to bind a socket to a specific network device or +adapter + +a call to accept() on a socket + +convert a string into a valid network address + +enumeration network devices or adapters + +invoking stat() on a file + +copying a file + +allocating storage for a file + +creating a hard link + +removing a file + +renaming a file + +opening a file + +creating a directory + +check fast resume data against files on disk + +an unknown exception + +allocate space for a piece in the cache + +move a part-file + +read from a part file + +write to a part-file + +a hostname lookup + +create or read a symlink + +handshake with a peer or server + +set socket option + +enumeration of network routes + +moving read/write position in a file, operation_t::hostname_lookup + +an async wait operation on a timer + +call to mmap() (or windows counterpart) + +call to ftruncate() (or SetEndOfFile() on windows) + +these constants are used to identify the operation that failed, causing a +peer to disconnect + +maps an operation id (from peer_error_alert and peer_disconnected_alert) +to its name. See operation_t for the constants + + + + + + + +the types an entry can have + +returns the concrete type of the entry + +constructors directly from a specific type. +The content of the argument is copied into the +newly constructed entry + + +construct an empty entry of the specified type. +see data_type enum. + +construct from bdecode_node parsed form (see bdecode()) + +copies the structure of the right hand side into this +entry. + + +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: + +The following code is equivalent, but a little bit shorter: + +To make it easier to extract information from a torrent file, the +class torrent_info exists. + +swaps the content of *this* with ``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``. + +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. + +returns a pretty-printed string representation +of the bencoded structure, with JSON-style syntax + +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. + +prints the bencoded structure to the ostream as a JSON-style structure. + + + + + + + +these match the socket types from socket_type.hpp +shifted one down + + + + + + + +``add()`` and ``remove()`` adds and removes a peer class to be added +to new peers based on socket type. + +``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. + +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``). + + +``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). + +natpmp can be NAT-PMP or PCP + + + + + + + +Truncates files larger than specified in the file_storage, saved under +the specified save_path. + +user defined alerts should use IDs greater than this + +this constant represents "max_alert_index" + 1 + +the total number of nodes and replacement nodes +in the routing table + +number of seconds since last activity + +struct to hold information about a single DHT routing table bucket + +returns the message associated with this alert + +The torrent_handle pointing to the torrent this +alert is associated with. + + +This is a base class for alerts that are associated with a +specific torrent. It contains a handle to the torrent. + +Note that by the time the client receives a torrent_alert, its +``handle`` member may be invalid. + + +The peer's IP address and port. + +the peer ID, if known. + +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``. + + +endpoint of the listen interface being announced + +returns a 0-terminated string of the tracker's URL + +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. + + + +'`userdata`` as set in add_torrent_params at torrent creation. +This can be used to associate this torrent with related data +in the client application more efficiently than info_hashes. + +The ``torrent_removed_alert`` is posted whenever a torrent is removed. Since +the torrent handle in its base class will usually be invalid (since the torrent +is already removed) it has the info hash as a member, to identify it. +It's posted when the ``alert_category::status`` bit is set in the alert_mask. + +Note that the ``handle`` remains valid for some time after +torrent_removed_alert is posted, as long as some internal libtorrent +task (such as an I/O task) refers to it. Additionally, other alerts like +save_resume_data_alert may be posted after torrent_removed_alert. +To synchronize on whether the torrent has been removed or not, call +torrent_handle::in_session(). This will return true before +torrent_removed_alert is posted, and false afterward. + +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. + + + + +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. + + +refers to the index of the file that completed. + +This is posted whenever an individual file completes its download. i.e. +All pieces overlapping this file have passed their hash check. + + + +returns the new and previous file name, respectively. + +refers to the index of the file that was renamed, + +This is posted as a response to a torrent_handle::rename_file() call, if the rename +operation succeeds. + + + +refers to the index of the file that was supposed to be renamed, +``error`` is the error code returned from the filesystem. + +This is posted as a response to a torrent_handle::rename_file() call, if the rename +operation failed. + +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. + +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. + +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. + +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. + +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``. + +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. + +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``. + + + +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. + + + + + + + +This alert is generated when a limit is reached that might have a negative impact on +upload or download rate performance. + + + +the new state of the torrent. + +the previous state. + +Generated whenever a torrent changes its state. + + + +This member says how many times in a row this tracker has failed. + +the error code indicating why the tracker announce failed. If it is +is ``lt::errors::tracker_failure`` the failure_reason() might contain +a more detailed description of why the tracker rejected the request. +HTTP status codes indicating errors are also set in this field. + + +if the tracker sent a "failure reason" string, it will be returned +here. + +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 message associated with this warning + +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. + + + +the data returned in the scrape response. These numbers +may be -1 if the response was malformed. + +This alert is generated when a scrape request succeeds. + + + +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()``. + +if the error indicates there is an associated message, this returns +that message. Otherwise and empty string. + +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. + + + +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. + +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. + + + + +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. + + + +specifies what event was sent to the tracker. See event_t. + +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. + + + + +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. + + + +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. + + + +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. + + + +This alert is generated when a peer is snubbed, when it stops sending data when we request +it. + + + +a 0-terminated string of the low-level operation that failed, or nullptr if +there was no low level disk operation. + +tells you what error caused this alert. + +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. + + + + + + +Tells you if the peer was incoming or outgoing + + +This alert is posted every time an incoming peer connection both +successfully passes the protocol handshake and is associated with a +torrent, or an outgoing peer connection attempt succeeds. For arbitrary +incoming connections, see incoming_connection_alert. + + + +the kind of socket this peer was connected over + +the operation or level where the error occurred. Specified as an +value from the operation_t enum. Defined in operations.hpp. + +tells you what error caused peer to disconnect. + +the reason the peer disconnected (if specified) + +This alert is generated when a peer is disconnected for any reason (other than the ones +covered by peer_error_alert ). + + + +the request we received from the peer + +true if we have this piece + +true if the peer indicated that it was interested to download before +sending the request + +if this is true, the peer is not allowed to download this piece because +of super-seeding rules. + +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. + + + +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. + + +the index of the piece that finished + +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 + + + +This alert is generated when a peer rejects or ignores a piece request. + + + +This alert is generated when a block request times out. + + + +This alert is generated when a block request receives a response. + + + +This alert is generated when a block request is sent to a peer. + + + + +This alert is generated when a block is received that was not requested or +whose request timed out. + + + +the path the torrent was moved to and from, respectively. + +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. + + + + +If the error happened for a specific file, this returns its path. + +this indicates what underlying operation caused the error + +The ``storage_moved_failed_alert`` is generated when an attempt to move the storage, +via torrent_handle::move_storage(), fails. + + + +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_hashes`` member +is hence the main way of identifying which torrent just completed the delete. + +This alert is generated when a request to delete the files of a torrent complete. + +This alert is posted in the ``alert_category::storage`` category, and that bit +needs to be set in the alert_mask. + + + +tells you why it failed. + +the info hash of the torrent whose files failed to be deleted + +This alert is generated when a request to delete the files of a torrent fails. +Just removing a torrent from the session cannot fail + + + +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(). + +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. + + + +the error code from the resume_data failure + +This alert is generated instead of ``save_resume_data_alert`` if there was an error +generating the resume data. ``error`` describes what went wrong. + + + +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. + + + +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. + + + +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 + + + +the error the web seed encountered. If this is not set, the server +sent an error message, call ``error_message()``. + +the URL the error is associated with + +in case the web server sent an error message, this function returns +it. + +This alert is generated when a HTTP seed name lookup fails. + + + +the error code describing the error. + +indicates which underlying operation caused the error + +the file that experienced the error + +If the storage fails to read or write files that it needs access to, this alert is +generated and the torrent is paused. + + + +indicates what failed when parsing the metadata. This error is +what's returned from lazy_bdecode(). + +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. + + + +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: + +the source address associated with the error (if any) + +the operation that failed + +the error code describing the error + +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. + + + +the IP address that is believed to be our external IP + +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. + + + +the network device libtorrent attempted to listen on, or the IP address + +the error the system returned + +the underlying operation that failed + +the type of listen socket this alert refers to. + +the address libtorrent attempted to listen on +see alert documentation for validity of this value + +the port libtorrent attempted to listen on +see alert documentation for validity of this value + +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. + + + +the address libtorrent ended up listening on. This address +refers to the local interface. + +the port libtorrent ended up listening on. + +the type of listen socket this alert refers to. + +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. + + + +refers to the mapping index of the port map that failed, i.e. +the index returned from add_mapping(). + +UPnP or NAT-PMP + +the local network the port mapper is running on + +tells you what failed. + +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. + + + +refers to the mapping index of the port map that failed, i.e. +the index returned from add_mapping(). + +the external port allocated for the mapping. + + + +the local network the port mapper is running on + +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. + + + + +the local network the port mapper is running on + +the message associated with this log line + +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. + + + + +If the error happened to a specific file, this returns the path to it. + +the underlying operation that failed + +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. + + + + + + + + + + + + +the reason for the peer being blocked. Is one of the values from the +reason_t enum. + +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) + + + + +This alert is generated when a DHT node announces to an info-hash on our +DHT node. It belongs to the ``alert_category::dht`` category. + + + + +This alert is generated when a DHT node sends a ``get_peers`` message to +our DHT node. It belongs to the ``alert_category::dht`` category. + + +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 ``alert_category::storage`` 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. + + + +This alert is generated when we receive a local service discovery message +from a peer for a torrent we're currently participating in. + + + +The tracker ID returned by the tracker + +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. + + + +This alert is posted when the initial DHT bootstrap is done. + + + +specifies which error the torrent encountered. + +the filename (or object) the error occurred on. + +This is posted whenever a torrent is transitioned into the error state. + + + +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. + + + +tells you what kind of socket the connection was accepted + +is the IP address and port the connection came from. + +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. + + + +This contains copies of the most important fields from the original +add_torrent_params object, passed to add_torrent() or +async_add_torrent(). Specifically, these fields are copied: + +* version +* ti +* name +* save_path +* userdata +* tracker_id +* flags +* info_hash + +the info_hash field will be updated with the info-hash of the torrent +specified by ``ti``. + +set to the error, if one occurred while adding the torrent. + +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. + + + +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. + +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 ``alert_category::status``, but it's not subject to +filtering, since it's only manually posted anyway. + + + +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. + +The session_stats_alert is posted when the user requests session statistics by +calling post_session_stats() on the session object. This alert does not +have a category, since it's only posted in response to an API call. It +is not subject to the alert_mask filter. + +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 + + + +the error code + +the operation that failed + +posted when something fails in the DHT. This is not necessarily a fatal +error, but it could prevent proper operation + + + +the target hash of the immutable item. This must +match the SHA-1 hash of the bencoded form of ``item``. + +the data for this item + +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. + + + +the public key that was looked up + +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. + +the sequence number of this item + +the salt, if any, used to lookup and store this item. If no +salt was used, this is an empty string + +the data for this item + +the last response for mutable data is authoritative. + +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. + + + +the target hash the item was stored under if this was an *immutable* +item. + +if a mutable item was stored, these are the public key, signature, +salt and sequence number the item was stored under. + +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. + +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. + + + +the error that occurred in the i2p SAM connection + +this alert is used to report errors in the i2p SAM connection + + + +the info_hash of the torrent we're looking for peers for. + +if this was an obfuscated lookup, this is the info-hash target +actually sent to the node. + +the endpoint we're sending this query to + +This alert is generated when we send a get_peers request +It belongs to the ``alert_category::dht`` category. + + + +returns the log message + +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. + + + +returns the log message + +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. + + + + + + +describes whether this log refers to in-flow or out-flow of the +peer. The exception is ``info`` which is neither incoming or outgoing. + + + +string literal indicating the kind of event. For messages, this is the +message name. + + +returns the log message + +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. + + + +the local network the corresponding local service discovery is running +on + +The error code + +posted if the local service discovery socket fails to start properly. +it's categorized as ``alert_category::error``. + +string literal indicating which kind of lookup this is + +the number of outstanding request to individual nodes +this lookup has right now + +the total number of requests that have timed out so far +for this lookup + +the total number of responses we have received for this +lookup so far for this lookup + +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. + +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. + +the number of seconds ago the +last message was sent that's still +outstanding + +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 + +the node-id or info-hash target for this lookup + +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 + + + +a vector of the currently running DHT lookups. + +contains information about every bucket in the DHT routing +table. + +the node ID of the DHT node instance + +the local socket this DHT node is running on + +contains current DHT state. Posted in response to session::post_dht_stats(). + + + +the request this peer sent to us + +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. + + + + + + + + + +the log message + +the module, or part, of the DHT that produced this log message. + +debug logging of the DHT when alert_category::dht_log is set in the alert +mask. + + + + + + +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. + +whether this is an incoming or outgoing packet. + +the DHT node we received this packet from, or sent this packet to +(depending on ``direction``). + +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. + + + + + + +Posted when we receive a response to a DHT get_peers request. + + + + + +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. + + + + +this is a bitmask of which features were enabled for this particular +pick. The bits are defined in the picker_flags_t enum. + + +this is posted when one or more blocks are picked by the piece picker, +assuming the verbose piece picker logging is enabled (see +alert_category::picker_log). + + + +The error code, if one is associated with this error + +this alert is posted when the session encounters a serious error, +potentially fatal + + + +the local DHT node's node-ID this routing table belongs to + +the number of nodes in the routing table and the actual nodes. + +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. + + + +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 + + + +id of the node the request was sent to (and this response was received from) + +the node the request was sent to (and this response was received from) + +the interval to wait before making another request to this node + +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. + +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()``. + +The total number of nodes returned by ``nodes()``. + +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. + +posted as a response to a call to session::dht_sample_infohashes() with +the information from the DHT response message. + + + +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 ``alert_category::upload`` category. + + + +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. + +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). + + + +the error + +the operation that failed + +the endpoint configured as the proxy + +this alert is posted with SOCKS5 related errors, when a SOCKS5 proxy is +configured. It's enabled with the alert_category::error alert category. + + + +the error + +the operation that failed + +posted when a prioritize_files() or file_priority() update of the file +priorities complete, which requires a round-trip to the disk thread. + +If the disk operation fails this alert won't be posted, but a +file_error_alert is posted instead, and the torrent is stopped. + + + +this alert may be posted when the initial checking of resume data and files +on disk (just existence, not piece hashes) completes. If a file belonging +to the torrent is found on disk, but is larger than the file in the +torrent, that's when this alert is posted. +the client may want to call truncate_files() in that case, or perhaps +interpret it as a sign that some other file is in the way, that shouldn't +be overwritten. + + + + +the name of the counter or gauge + +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. + +describes one statistics metric from the session. For more information, +see the session-statistics_ section. + +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()). + +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. + + +indicates that IPs in this range should not be connected +to nor accepted as incoming connections + +the flags defined for an IP range + +returns true if the filter does not contain any rules + +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. + +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. + +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. + +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. + + +this flag indicates that destination ports in the +range should not be connected to + +the defined flags for a port range + +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. + +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. + +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. + + + +returns the new value + + + +this is a simple posix disk I/O back-end, used for systems that don't +have a 64 bit virtual address space or don't support memory mapped files. +It's implemented using portable C file functions and is single-threaded. + +The original BitTorrent version, using SHA-1 hashes + +Version 2 of the BitTorrent protocol, using SHA-256 hashes + + +BitTorrent version enumerator + +The default constructor creates an object that has neither a v1 or v2 +hash. + +For backwards compatibility, make it possible to construct directly +from a v1 hash. This constructor allows *implicit* conversion from a +v1 hash, but the implicitness is deprecated. + +returns true if the corresponding info hash is present in this +object. + +returns the has for the specified protocol version + +returns the v2 (truncated) info-hash, if there is one, otherwise +returns the v1 info-hash + + + +calls the function object ``f`` for each hash that is available. +starting with v1. The signature of ``F`` is:: + + void(sha1_hash, protocol_version); + + + + +class holding the info-hash of a torrent. It can hold a v1 info-hash +(SHA-1) or a v2 info-hash (SHA-256) or both. + + + If ``has_v2()`` is false then the v1 hash might actually be a truncated + v2 hash + +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 + +Enables alerts when peers send invalid requests, get banned or +snubbed. + +Enables alerts for port mapping events. For NAT-PMP and UPnP. + +Enables alerts for events related to the storage. File errors and +synchronization events for moving the storage, renaming files etc. + +Enables all tracker events. Includes announcing to trackers, +receiving responses, warnings and errors. + +Low level alerts for when peers are connected and disconnected. + +Enables alerts for when a torrent or the session changes state. + +Alerts when a peer is blocked by the ip blocker or port blocker. + +Alerts when some limit is reached that might limit the download +or upload rate. + +Alerts on events in the DHT node. For incoming searches or +bootstrapping being done etc. + +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. + +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. + +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. + +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. + +enables the incoming_request_alert. + +enables dht_log_alert, debug logging for the DHT + +enable events from pure dht operations not related to torrents + +enables port mapping log events. This log is useful +for debugging the UPnP or NAT-PMP implementation + +enables verbose logging from the piece picker. + +alerts when files complete downloading + +alerts when pieces complete downloading or fail hash check + +alerts when we upload blocks to other peers + +alerts on individual blocks being requested, downloading, finished, +rejected, time-out and cancelled. This is likely to post alerts at a +high rate. + +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. + + + +a timestamp is automatically created in the constructor + +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: + +returns a string literal describing the type of the alert. It does +not include any information that might be bundled with the alert. + +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. + +returns a bitmask specifying which categories this alert belong to. + +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()) + +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. + + ``alert_cast<>`` can only cast to an exact alert type, not a base class + +All pieces will be written to their final position, all files will be +allocated in full when the torrent is first started. This mode minimizes +fragmentation but could be a costly operation. + +All pieces will be written to the place where they belong and sparse files +will be used. This is the recommended, and default mode. + +types of storage allocation used for add_torrent_params::storage_mode. + + + + + +this is not an enum value, but a flag that can be set in the return +from async_check_files, in case an existing file was found larger than +specified in the torrent. i.e. it has garbage at the end +the status_t field is used for this to preserve ABI. + +return values from check_fastresume, and move_storage + +replace any files in the destination when copying +or moving the storage + +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. + +if any file exist in the target, take those files instead +of the ones we may have in the source. + +flags for async_move_storage + + + +a parameter pack used to construct the storage for a torrent, used in +disk_interface + +open the file for reading only + +open the file for writing only + +open the file for reading and writing + +the mask for the bits determining read or write mode + +open the file in sparse mode (if supported by the +filesystem). + +don't update the access timestamps on the file (if +supported by the operating system and filesystem). +this generally improves disk performance. + +open the file for random access. This disables read-ahead +logic + +the index of the file this entry refers to into the ``file_storage`` +file list of this torrent. This starts indexing at 0. + +``open_mode`` is a bitmask of the file flags this file is currently +opened with. 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. + +a (high precision) timestamp of when the file was last used. + +this contains information about a file that's currently open by the +libtorrent disk I/O subsystem. It's associated with a single torrent. + +force making a copy of the cached block, rather than getting a +reference to a block already in the cache. This is used the block is +expected to be overwritten very soon, by async_write()`, and we need +access to the previous content. + +hint that there may be more disk operations with sequential access to +the file + +don't keep the read block in cache. This is a hint that this block is +unlikely to be read again anytime soon, and caching it would be +wasteful. + +compute a v1 piece hash. This is only used by the async_hash() call. +If this flag is not set in the async_hash() call, the SHA-1 piece +hash does not need to be computed. + +this flag instructs a hash job that we just completed this piece, and +it should be flushed to disk + +this is called when a new torrent is added. The shared_ptr can be +used to hold the internal torrent object alive as long as there are +outstanding disk operations on the storage. +The returned storage_holder is an owning reference to the underlying +storage that was just created. It is fundamentally a storage_index_t + +remove the storage with the specified index. This is not expected to +delete any files from disk, just to clean up any resources associated +with the specified storage. + +perform a read or write operation from/to the specified storage +index and the specified request. When the operation completes, call +handler possibly with a disk_buffer_holder, holding the buffer with +the result. Flags may be set to affect the read operation. See +disk_job_flags_t. + +The disk_observer is a callback to indicate that +the store buffer/disk write queue is below the watermark to let peers +start writing buffers to disk again. When ``async_write()`` returns +``true``, indicating the write queue is full, the peer will stop +further writes and wait for the passed-in ``disk_observer`` to be +notified before resuming. + +Note that for ``async_read``, the peer_request (``r``) is not +necessarily aligned to blocks (but it is most of the time). However, +all writes (passed to ``async_write``) are guaranteed to be block +aligned. + +Compute hash(es) for the specified piece. Unless the v1_hash flag is +set (in ``flags``), the SHA-1 hash of the whole piece does not need +to be computed. + +The `v2` span is optional and can be empty, which means v2 hashes +should not be computed. If v2 is non-empty it must be at least large +enough to hold all v2 blocks in the piece, and this function will +fill in the span with the SHA-256 block hashes of the piece. + +computes the v2 hash (SHA-256) of a single block. The block at +``offset`` in piece ``piece``. + +called to request the files for the specified storage/torrent be +moved to a new location. It is the disk I/O object's responsibility +to synchronize this with any currently outstanding disk operations to +the storage. Whether files are replaced at the destination path or +not is controlled by ``flags`` (see move_flags_t). + +This is called on disk I/O objects to request they close all open +files for the specified storage/torrent. If file handles are not +pooled/cached, it can be a no-op. For truly asynchronous disk I/O, +this should provide at least one point in time when all files are +closed. It is possible that later asynchronous operations will +re-open some of the files, by the time this completion handler is +called, that's fine. + +this is called when torrents are added to validate their resume data +against the files on disk. This function is expected to do a few things: + +if ``links`` is non-empty, it contains a string for each file in the +torrent. The string being a path to an existing identical file. The +default behavior is to create hard links of those files into the +storage of the new torrent (specified by ``storage``). An empty +string indicates that there is no known identical file. This is part +of the "mutable torrent" feature, where files can be reused from +other torrents. + +The ``resume_data`` points the resume data passed in by the client. + +If the ``resume_data->flags`` field has the seed_mode flag set, all +files/pieces are expected to be on disk already. This should be +verified. Not just the existence of the file, but also that it has +the correct size. + +Any file with a piece set in the ``resume_data->have_pieces`` bitmask +should exist on disk, this should be verified. Pad files and files +with zero priority may be skipped. + +This is called when a torrent is stopped. It gives the disk I/O +object an opportunity to flush any data to disk that's currently kept +cached. This function should at least do the same thing as +async_release_files(). + +This function is called when the name of a file in the specified +storage has been requested to be renamed. The disk I/O object is +responsible for renaming the file without racing with other +potentially outstanding operations against the file (such as read, +write, move, etc.). + +This function is called when some file(s) on disk have been requested +to be removed by the client. ``storage`` indicates which torrent is +referred to. See session_handle for ``remove_flags_t`` flags +indicating which files are to be removed. +e.g. session_handle::delete_files - delete all files +session_handle::delete_partfile - only delete part file. + +This is called to set the priority of some or all files. Changing the +priority from or to 0 may involve moving data to and from the +partfile. The disk I/O object is responsible for correctly +synchronizing this work to not race with any potentially outstanding +asynchronous operations affecting these files. + +``prio`` is a vector of the file priority for all files. If it's +shorter than the total number of files in the torrent, they are +assumed to be set to the default priority. + +This is called when a piece fails the hash check, to ensure there are +no outstanding disk operations to the piece before blocks are +re-requested from peers to overwrite the existing blocks. The disk I/O +object does not need to perform any action other than synchronize +with all outstanding disk operations to the specified piece before +posting the result back. + +update_stats_counters() is called to give the disk storage an +opportunity to update gauges in the ``c`` stats counters, that aren't +updated continuously as operations are performed. This is called +before a snapshot of the counters are passed to the client. + +Return a list of all the files that are currently open for the +specified storage/torrent. This is is just used for the client to +query the currently open files, and which modes those files are open +in. + +this is called when the session is starting to shut down. The disk +I/O object is expected to flush any outstanding write jobs, cancel +hash jobs and initiate tearing down of any internal threads. If +``wait`` is true, this should be asynchronous. i.e. this call should +not return until all threads have stopped and all jobs have either +been aborted or completed and the disk I/O object is ready to be +destructed. + +This will be called after a batch of disk jobs has been issues (via +the ``async_*`` ). It gives the disk I/O object an opportunity to +notify any potential condition variables to wake up the disk +thread(s). The ``async_*`` calls can of course also notify condition +variables, but doing it in this call allows for batching jobs, by +issuing the notification once for a collection of jobs. + +This is called to notify the disk I/O object that the settings have +been updated. In the disk io constructor, a settings_interface +reference is passed in. Whenever these settings are updated, this +function is called to allow the disk I/O object to react to any +changed settings relevant to its operations. + +The disk_interface is the customization point for disk I/O in libtorrent. +implement this interface and provide a factory function to the session constructor +use custom disk I/O. All functions on the disk subsystem (implementing +disk_interface) are called from within libtorrent's network thread. For +disk I/O to be performed in a separate thread, the disk subsystem has to +manage that itself. + +Although the functions are called ``async_*``, they do not technically +*have* to be asynchronous, but they support being asynchronous, by +expecting the result passed back into a callback. The callbacks must be +posted back onto the network thread via the io_context object passed into +the constructor. The callbacks will be run in the network thread. + + + + + + + + +a unique, owning, reference to the storage of a torrent in a disk io +subsystem (class that implements disk_interface). This is held by the +internal libtorrent torrent object to tie the storage object allocated +for a torrent to the lifetime of the internal torrent object. When a +torrent is removed from the session, this holder is destructed and will +inform the disk object. + + + + + + + + + + + + + + + + + + + + + + + + +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 + + + + + + +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. + +This block has not been downloaded or requested form any peer. + +The block has been requested, but not completely downloaded yet. + +The block has been downloaded and is currently queued for being +written to disk. + +The block has been written to disk. + +this is the enum used for the block_info::state field. + +The peer is the ip address of the peer this block was downloaded from. + +the number of bytes that have been received for this block + +the total number of bytes in this block. + +the state this block is in (see block_state_t) + +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. + +holds the state of a block in a piece. Who we requested +it from and how far along we are at downloading it. + +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. + +the number of blocks in this piece + +the number of blocks that are in the finished state + +the number of blocks that are in the writing state + +the number of blocks that are in the requested state + +this is an array of ``blocks_in_piece`` number of +items. One for each block in the piece. + + that's owned by the session object. The next time + get_download_queue() is called, it will be invalidated. + +This class holds information about pieces that have outstanding requests +or outstanding writes + +for std::hash (and to support using this type in unordered_map etc.) + + +constructs a torrent handle that does not refer to a torrent. +i.e. is_valid() will return false. + +instruct libtorrent to overwrite any data that may already have been +downloaded with the data of the new piece being added. Using this +flag when adding a piece that is actively being downloaded from other +peers may have some unexpected consequences, as blocks currently +being downloaded from peers may not be replaced. + +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. + +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. + +Returns true if this piece has been completely downloaded and written +to disk, and false otherwise. + +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. + +calculates ``distributed_copies``, ``distributed_full_copies`` and +``distributed_fraction``. + +includes partial downloaded blocks in ``total_done`` and +``total_wanted_done``. + +includes ``last_seen_complete``. + +populate the ``pieces`` field in torrent_status. + +includes ``verified_pieces`` (only applies to torrents in *seed +mode*). + +includes ``torrent_file``, which is all the static information from +the .torrent file. + +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. + +includes ``save_path``, the path to the directory the files of the +torrent are saved to. + +``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. + +``get_download_queue()`` returns a vector with information about pieces +that are partially downloaded or not downloaded but partially +requested. See partial_piece_info for the fields in the returned +vector. + +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. + +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. + +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. + +This function fills in the supplied vector, or returns a 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 currently +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. + +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 + +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. + +``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. + +``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. + +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. + +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. + +``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. + +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 +removed from the session AND destructed. + +To tell if the torrent_handle is in the session, use +torrent_handle::in_session(). This will return true before +session_handle::remove_torrent() is called, and false +afterward. + +Clients should only use is_valid() to determine if the result of +session::find_torrent() was successful. + +Unlike other member functions which return a value, is_valid() +completes immediately, without blocking on a result from the +network thread. Also unlike other functions, it never throws +the system_error exception. + +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. + +``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. + +For possible values of the ``flags`` parameter, see pause_flags_t. + +To know if a torrent is paused or not, call +``torrent_handle::flags()`` and check for the +``torrent_status::paused`` flag. + + 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_. + + +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. + +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. + +``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. + +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. + +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). + +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. + +``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. + + If you pause every torrent individually instead of pausing the + session, every torrent will have its paused state saved in the + resume data! + + 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: + + 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. + +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. + + 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. + +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. + +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 + +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. + +torrent_file() 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. + +Note that the torrent_info object returned here may be a different +instance than the one added to the session, with different attributes +like piece layers, dht nodes and trackers. A torrent_info object does +not round-trip cleanly when added to a session. + +This means if you want to create a .torrent file by passing the +torrent_info object into create_torrent, you need to use +torrent_file_with_hashes() instead. + +torrent_file_with_hashes() returns a *copy* of the internal +torrent_info and piece layer hashes (if it's a v2 torrent). The piece +layers will only be included if they are available. If this torrent +was added from a .torrent file with piece layers or if it's seeding, +the piece layers are available. This function is more expensive than +torrent_file() since it needs to make copies of this information. + +When constructing a create_torrent object from a torrent_info that's +in a session, you need to use this function. + +Note that a torrent added from a magnet link may not have the full +merkle trees for all files, and hence not have the complete piece +layers. In that state, you cannot create a .torrent file even from +the torrent_info returned from torrent_file_with_hashes(). Once the +torrent completes downloading all files, becoming a seed, you can +make a .torrent file from it. + +returns the piece layers for all files in the torrent. If this is a +v1 torrent (and doesn't have any piece layers) it returns an empty +vector. This is a blocking call that will synchronize with the +libtorrent network thread. + +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. + +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. + +``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. +To synchronize with the priorities taking effect, wait for the +file_prio_alert. + +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. + +Moving data from a file into the part file is currently not +supported. If a file has its priority set to 0 *after* it has already +been created, it will not be moved into the partfile. + +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. + +``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. + +``force_lsd_announce`` will announce the torrent on LSD +immediately. + +``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. + +``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_. + +``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. + +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. + +``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. + +``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. + +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. + +When copying files, sparse regions are not likely to be preserved. +This makes it proportionally more expensive to move a large torrent +when only few pieces have been downloaded, since the files are then +allocated with zeros in the destination directory. + +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. + +returns the info-hash(es) 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. +The ``info_hash()`` returns the SHA-1 info-hash for v1 torrents and a +truncated hash for v2 torrents. For the full v2 info-hash, use +``info_hashes()`` instead. + +comparison operators. The order of the torrents is unspecified +but stable. + +returns a unique identifier for this torrent. It's not a dense index. +It's not preserved across sessions. + +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. + +returns the userdata pointer as set in add_torrent_params + +Returns true if the torrent is in the session. It returns true before +session::remove_torrent() is called, and false afterward. + +Note that this is a blocking function, unlike torrent_handle::is_valid() +which returns immediately. + +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. + + 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``. + + 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. + + +filled in by the constructor and should be left untouched. It is used +for forward binary compatibility. + +torrent_info object with the torrent to add. Unless the +info_hash is set, this is required to be initialized. + +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. + +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. + +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. + +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. + +the path where the torrent is or will be stored. + + 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. + +One of the values from storage_mode_t. For more information, see +storage-allocation_. + +The ``userdata`` parameter is optional and will be passed on to the +extension constructor functions, if any +(see torrent_handle::add_extension()). It will also be stored in the +torrent object and can be retrieved by calling userdata(). + +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. + +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. + +flags controlling aspects of this torrent and how it's added. See +torrent_flags_t for details. + + 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. + +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. + +``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_. + +the upload and download rate limits for this torrent, specified in +bytes per second. -1 means unlimited. + +the total number of bytes uploaded and downloaded by this torrent so +far. + +the number of seconds this torrent has spent in started, finished and +seeding state so far, respectively. + +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. + +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. + +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. + +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`_. + +peers to add to the torrent, to be tried to be connected to as +bittorrent peers. + +peers banned from this torrent. The will not be connected to + +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. + +this is a bitfield indicating which pieces we already have of this +torrent. + +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. + +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. + +v2 hashes, if known + +if set, indicates which hashes are included in the corresponding +vector of ``merkle_trees``. These bitmasks always cover the full +tree, a cleared bit means the hash is all zeros (i.e. not set) and +set bit means the next hash in the corresponding vector in +``merkle_trees`` is the hash for that node. This is an optimization +to avoid storing a lot of zeros. + +bit-fields indicating which v2 leaf hashes have been verified +against the root hash. If this vector is empty and merkle_trees is +non-empty it implies that all hashes in merkle_trees are verified. + +this is a map of file indices in the torrent and new filenames to be +applied before the torrent is added. + +the posix time of the last time payload was received or sent for this +torrent, respectively. + +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(). + +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_. + + +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. + +``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. + +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. + +not used by libtorrent. It's intended as a potentially user-facing +identifier of this peer class. + +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. + +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. + +holds settings for a peer class. Used in set_peer_class() and +get_peer_class() calls. + +Don't download the file or piece. Partial pieces may still be downloaded when +setting file priorities. + +The default priority for files and pieces. + +The lowest priority for files and pieces. + +The highest priority for files and pieces. + +construct a nullptr client data + + + + + +we don't allow type-unsafe operations + +A thin wrapper around a void pointer used as "user data". i.e. an opaque +cookie passed in to libtorrent and returned on demand. It adds type-safety by +requiring the same type be requested out of it as was assigned to it. + + + + + + + + + + +A type describing kinds of sockets involved in various operations or events. + +the index of the file + +the offset from the start of the file, in bytes + +the size of the window, in bytes + +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. + +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. + +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. + +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. + +``root_hash`` is an optional pointer to a 32 byte SHA-256 hash, being +the merkle tree root hash for this file. This is only used for v2 +torrents. If the ``root hash`` is specified for one file, it has to +be specified for all, otherwise this function will fail. +Note that the buffer ``root_hash`` points to must out-live the +file_storage object, it will not be copied. This parameter is only +used when *loading* torrents, that already have their file hashes +computed. When creating torrents, the file hashes will be computed by +the piece hashes. + +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``. + +The overloads that take an `error_code` reference will report failures +via that variable, otherwise `system_error` is thrown. + +renames the file at ``index`` to ``new_filename``. Keep in mind +that filenames are expected to be UTF-8 encoded. + +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. + +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. + +returns the number of files in the file_storage + +returns the index of the one-past-end file in the file storage + +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. + +returns the total number of bytes all the files in this torrent spans + +set and get the number of pieces in the torrent + +returns the index of the one-past-end piece in the file storage + +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). + +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. + +set and get the size of each piece in this torrent. It must be a power of two +and at least 16 kiB. + +returns the piece size of ``index``. This will be the same as piece_length(), except +for the last piece, which may be shorter. + +Returns the size of the given piece. If the piece spans multiple files, +only the first file is considered part of the piece. This is used for +v2 torrents, where all files are piece aligned and padded. i.e. The pad +files are not considered part of the piece for this purpose. + +returns the number of blocks in the specified piece, for v2 torrents. + +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. + +swap all content of *this* with *ti*. + +arrange files and padding to match the canonical form required +by BEP 52 + +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. + +``root()`` returns the SHA-256 merkle tree root of the specified file, +in case this is a v2 torrent. Otherwise returns zeros. +``root_ptr()`` returns a pointer to the SHA-256 merkle tree root hash +for the specified file. The pointer points into storage referred to +when the file was added, it is not owned by this object. Torrents +that are not v2 torrents return nullptr. + +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). + +Returns the number of pieces or blocks the file at `index` spans, +under the assumption that the file is aligned to the start of a piece. +This is only meaningful for v2 torrents, where files are guaranteed +such alignment. +These numbers are used to size and navigate the merkle hash tree for +each file. + +index of first piece node in the merkle tree + +returns the crc32 hash of file_path(index) + +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. + +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. + +this file has the hidden attribute set. This is primarily +a windows attribute + +this file has the executable attribute set. + +this file is a symbolic link. It should have a link +target string associated with it. + +returns a bitmask of flags from file_flags_t that apply +to file at ``index``. + +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. + +returns the index of the file at the given offset in the torrent + +finds the file with the given root hash and returns its index +if there is no file with the root hash, file_index_t{-1} is returned + +returns the piece index the given file starts at + +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. + +returns true if this torrent contains v2 metadata. + +The ``file_storage`` class represents a file list and the piece +size. Everything necessary to interpret a regular bittorrent storage +file structure. + +get the ``error_category`` for zip errors + +Not an error + +the supplied gzip buffer has invalid header + +the gzip buffer would inflate to more bytes than the specified +maximum size, and was rejected. + +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 + +an unknown error occurred during gzip inflation + +the number of error codes + +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. + +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. + +include this bit if your plugin needs to have on_tick() called + +include this bit if your plugin needs to have on_dht_request() +called + +include this bit if your plugin needs to have on_alert() +called + +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. + +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 client_data_t 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). + +called when plugin is added to a session + +called when the session is aborted +the plugin should perform any cleanup necessary to allow the session's +destruction (e.g. cancel outstanding async operations) + +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(). + +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(). + +return true if the add_torrent_params should be added + +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(). + +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. + + +called on startup while loading settings state from the session_params + +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. + +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. + +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``. + +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. + +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. + +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. + +called when the torrent changes state +the state is one of torrent_status::state_t +enum members + +this is the first time we see this peer + +this peer was not added because it was +filtered by the IP filter + +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. + +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. + +This function is expected to return the name of +the plugin. + +can add entries to the extension handshake +this is not called for web seeds + +called when the peer is being disconnected. + +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. + +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 + +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 + +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. + +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. + + + +called after a choke message has been sent to the peer + +called after piece data has been sent to the peer +this can be used for stats book keeping + +called when libtorrent think this peer should be disconnected. +if the plugin returns false, the peer will not be disconnected. + +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. + +this is not called for web seeds + +called when a piece that this peer participated in either +fails or passes the hash_check + +called approximately once every second + +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. + +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 + + + +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 + + +A human readable 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. This is UTF-8 +encoded. + +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). + +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. + +the time since we last sent a request to this peer and since any +transfer occurred with this peer + +the time until all blocks in the request queue will be downloaded + +**we** are interested in pieces from this peer. + +**we** have choked this peer. + +the peer is interested in **us** + +the peer has choked **us**. + +means that this peer supports the +`extension protocol`__. + +__ extension_protocol.html + +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. + +deprecated synonym for outgoing_connection + +The connection is opened, and waiting for the +handshake. Until the handshake is done, the peer +cannot be identified. + +The connection is in a half-open state (i.e. it is +being connected). + +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. + +This peer is a seed (it has all the pieces). + +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. + +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. + +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. + +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. + +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. + +indicates that this socket is running on top of the +I2P transport. + +indicates that this socket is a uTP socket + +indicates that this socket is running on top of an SSL +(TLS) channel + +this connection is obfuscated with RC4 + +the handshake of this connection was obfuscated +with a Diffie-Hellman exchange + +tells you in which state the peer is in. It is set to +any combination of the peer_flags_t flags above. + +The peer was received from the tracker. + +The peer was received from the kademlia DHT. + +The peer was received from the peer exchange +extension. + +The peer was received from the local service +discovery (The peer is on the local network). + +The peer was added from the fast resume data. + +we received an incoming connection from this peer + +a combination of flags describing from which sources this peer +was received. A combination of the peer_source_flags_t above. + +the current upload and download speed we have to and from this peer +(including any protocol messages). updated about once per second + +The transfer rates of payload data only updated about once per second + +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()_ + +the number of bytes we have requested from this peer, but not yet +received. + +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. + +the number of bytes allocated +and used for the peer's send buffer, respectively. + +the number of bytes +allocated and used as receive buffer, respectively. + +the number of pieces this peer has participated in sending us that +turned out to fail the hash check. + +this is the number of requests we have sent to this peer that we +haven't got a response for yet + +the number of block requests that have timed out, and are still in the +download queue + +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 + +the number of requests messages that are currently in the send buffer +waiting to be sent. + +the number of requests that is tried to be maintained (this is +typically a function of download speed) + +the number of piece-requests we have received from this peer +that we haven't answered with a piece yet. + +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. + +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. + +Regular bittorrent connection + +HTTP connection using the `BEP 19`_ protocol + +HTTP connection using the `BEP 17`_ protocol + +the kind of connection this peer uses. See connection_type_t. + +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``. + +number of outstanding bytes to read +from disk + +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. + +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. + +the number of pieces this peer has. + +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. + +the progress of the peer in the range [0, 1]. This is always 0 when +floating point operations are disabled, instead use ``progress_ppm``. + +indicates the download progress of the peer in the range [0, 1000000] +(parts per million). + +the IP-address to this peer. The type is an asio endpoint. For +more info, see the asio_ documentation. + + +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. + +The peer is not waiting for any external events to +send or receive data. + +The peer is waiting for the rate limiter. + +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. + +The peer is waiting for the disk I/O thread to catch +up writing buffers to disk before downloading more. + +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. + +holds information and statistics about one peer +that libtorrent is connected to + + + +http seeds are different from url seeds in the +protocol they use. http seeds follows the original +http seed spec. by John Hoffman + +URL and type comparison + +URL and type less-than comparison + +The URL of the web seed + +Optional authentication. If this is set, it's passed +in as HTTP basic auth to the web seed. The format is: +username:password. + +Any extra HTTP headers that need to be passed to the web seed + +The type of web seed (see type_t) + +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`_. + +the max size of a .torrent file to load into RAM + +the max number of pieces allowed in the torrent + +the max recursion depth in the bdecoded structure + +the max number of bdecode tokens + +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. + +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``. + +frees all storage associated with this torrent_info object + +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. + +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. + + Using `remap_files()` is discouraged as it's incompatible with v2 + torrents. This is because the piece boundaries and piece hashes in + v2 torrents are intimately tied to the file boundaries. Instead, + just rename individual files, or implement a custom disk_interface + to customize how to store files. + +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. + +``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. + +``trackers()`` returns all entries from announce-list. + +``clear_trackers()`` removes all trackers from announce-list. + +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. + +``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. + +``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. + +``total_size()`` returns the total number of bytes the torrent-file +represents. Note that this is the number of pieces times the piece +size (modulo the last piece possibly being smaller). With pad files, +the total size will be larger than the sum of all (regular) file +sizes. + +``piece_length()`` and ``num_pieces()`` returns 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. + +``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. + +returns the info-hash of the torrent. For BitTorrent v2 support, use +``info_hashes()`` to get an object that may hold both a v1 and v2 +info-hash + +returns whether this torrent has v1 and/or v2 metadata, respectively. +Hybrid torrents have both. These are shortcuts for +info_hashes().has_v1() and info_hashes().has_v2() calls. + +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. + +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. + +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()``. + +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. + +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. + +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. + +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. + +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. + +``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. + + +``name()`` returns the name of the torrent. +name contains UTF-8 encoded string. + +``creation_date()`` returns the creation date of the torrent as time_t +(`posix time`_). If there's no time stamp in the torrent file, 0 is +returned. + +``creator()`` returns the creator string in the torrent. If there is +no creator string it will return an empty string. + +``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. + +If this torrent contains any DHT nodes, they are put in this vector in +their original form (host name and port number). + +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. + +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 `max_pieces` parameter allows limiting the amount of memory +dedicated to loading the torrent, and fails for torrents that exceed +the limit. To load large torrents, this limit may also need to be +raised in settings_pack::max_piece_count and in calls to +read_resume_data(). + +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. + +returns a the raw info section of the torrent file. +The underlying buffer is still owned by the torrent_info object + +return the bytes of the piece layer hashes for the specified file. If +the file doesn't have a piece layer, an empty span is returned. +The span size is divisible by 32, the size of a SHA-256 hash. +If the size of the file is smaller than or equal to the piece size, +the files "root hash" is the hash of the file and is not saved +separately in the "piece layers" field, but this function still +returns the root hash of the file in that case. + +clears the piece layers from the torrent_info. This is done by the +session when a torrent is added, to avoid storing it twice. The piece +layer (or other hashes part of the merkle tree) are stored in the +internal torrent object. + +the torrent_info class holds the information found in a .torrent file. + +This constructor can be used to start with the default plugins +(ut_metadata, ut_pex and smart_ban). Pass a settings_pack to set the +initial settings when the session starts. + +This constructor helps to configure the set of initial plugins +to be added to the session before it's started. + +The settings to configure the session with + +the plugins to add to the session as it is constructed + +DHT node ID and node addresses to bootstrap the DHT with. + +function object to construct the storage object for DHT items. + +function object to create the disk I/O subsystem. Defaults to +default_disk_io_constructor. + +this container can be used by extensions/plugins to store settings. It's +primarily here to make it convenient to save and restore state across +sessions, using read_session_params() and write_session_params(). + +the IP filter to use for the session. This restricts which peers are allowed +to connect. As if passed to set_ip_filter(). + +The session_params is a parameters pack for configuring the session +before it's started. + +These functions serialize and de-serialize a ``session_params`` object to and +from bencoded form. The session_params object is used to initialize a new +session using the state from a previous one (or by programmatically configure +the session up-front). +The flags parameter can be used to only save and load certain aspects of the +session's state. +The ``_buf`` suffix indicates the function operates on buffer rather than the +bencoded structure. +The torrents in a session are not part of the session_params state, they have +to be restored separately. + +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_. + + +The ``major``, ``minor``, ``revision`` and ``tag`` parameters are used to +identify the version of your client. + +Not an error + +Two torrents has files which end up overwriting each other + +A piece did not match its piece hash + +The .torrent file does not contain a bencoded dictionary at +its top level + +The .torrent file does not have an ``info`` dictionary + +The .torrent file's ``info`` entry is not a dictionary + +The .torrent file does not have a ``piece length`` entry + +The .torrent file does not have a ``name`` entry + +The .torrent file's name entry is invalid + +The length of a file, or of the whole .torrent file is invalid. +Either negative or not an integer + +Failed to parse a file entry in the .torrent + +The ``pieces`` field is missing or invalid in the .torrent file + +The ``pieces`` string has incorrect length + +The .torrent file has more pieces than is supported by libtorrent + +The metadata (.torrent file) that was received from the swarm +matched the info-hash, but failed to be parsed + +The file or buffer is not correctly bencoded + +The .torrent file does not contain any files + +The string was not properly url-encoded as expected + +Operation is not permitted since the session is shutting down + +There's already a torrent with that info-hash added to the +session + +The supplied torrent_handle is not referring to a valid torrent + +The type requested from the entry did not match its type + +The specified URI does not contain a valid info-hash + +One of the files in the torrent was unexpectedly small. This +might be caused by files being changed by an external process + +The URL used an unknown protocol. Currently ``http`` and +``https`` (if built with openssl support) are recognized. For +trackers ``udp`` is recognized as well. + +The URL did not conform to URL syntax and failed to be parsed + +The peer sent a piece message of length 0 + +A bencoded structure was corrupt and failed to be parsed + +The fast resume file was missing or had an invalid file version +tag + +The fast resume file was missing or had an invalid info-hash + +The info-hash did not match the torrent + +The URL contained an invalid hostname + +The URL had an invalid port + +The port is blocked by the port-filter, and prevented the +connection + +The IPv6 address was expected to end with "]" + +The torrent is being destructed, preventing the operation to +succeed + +The connection timed out + +The peer is upload only, and we are upload only. There's no point +in keeping the connection + +The peer is upload only, and we're not interested in it. There's +no point in keeping the connection + +The peer sent an unknown info-hash + +The torrent is paused, preventing the operation from succeeding + +The peer sent an invalid have message, either wrong size or +referring to a piece that doesn't exist in the torrent + +The bitfield message had the incorrect size + +The peer kept requesting pieces after it was choked, possible +abuse attempt. + +The peer sent a piece message that does not correspond to a +piece request sent by the client + +memory allocation failed + +The torrent is aborted, preventing the operation to succeed + +The peer is a connection to ourself, no point in keeping it + +The peer sent a piece message with invalid size, either negative +or greater than one block + +The peer has not been interesting or interested in us for too +long, no point in keeping it around + +The peer has not said anything in a long time, possibly dead + +The peer did not send a handshake within a reasonable amount of +time, it might not be a bittorrent peer + +The peer has been unchoked for too long without requesting any +data. It might be lying about its interest in us + +The peer sent an invalid choke message + +The peer send an invalid unchoke message + +The peer sent an invalid interested message + +The peer sent an invalid not-interested message + +The peer sent an invalid piece request message + +The peer sent an invalid hash-list message (this is part of the +merkle-torrent extension) + +The peer sent an invalid hash-piece message (this is part of the +merkle-torrent extension) + +The peer sent an invalid cancel message + +The peer sent an invalid DHT port-message + +The peer sent an invalid suggest piece-message + +The peer sent an invalid have all-message + +The peer sent an invalid have none-message + +The peer sent an invalid reject message + +The peer sent an invalid allow fast-message + +The peer sent an invalid extension message ID + +The peer sent an invalid message ID + +The synchronization hash was not found in the encrypted handshake + +The encryption constant in the handshake is invalid + +The peer does not support plain text, which is the selected mode + +The peer does not support RC4, which is the selected mode + +The peer does not support any of the encryption modes that the +client supports + +The peer selected an encryption mode that the client did not +advertise and does not support + +The pad size used in the encryption handshake is of invalid size + +The encryption handshake is invalid + +The client is set to not support incoming encrypted connections +and this is an encrypted connection + +The client is set to not support incoming regular bittorrent +connections, and this is a regular connection + +The client is already connected to this peer-ID + +Torrent was removed + +The packet size exceeded the upper sanity check-limit + + +The web server responded with an error + +The web server response is missing a location header + +The web seed redirected to a path that no longer matches the +.torrent directory structure + +The connection was closed because it redirected to a different +URL + +The HTTP range header is invalid + +The HTTP response did not have a content length + +The IP is blocked by the IP filter + +At the connection limit + +The peer is marked as banned + +The torrent is stopping, causing the operation to fail + +The peer has sent too many corrupt pieces and is banned + +The torrent is not ready to receive peers + +The peer is not completely constructed yet + +The session is closing, causing the operation to fail + +The peer was disconnected in order to leave room for a +potentially better peer + +The torrent is finished + +No UPnP router found + +The metadata message says the metadata exceeds the limit + +The peer sent an invalid metadata request message + +The peer advertised an invalid metadata size + +The peer sent a message with an invalid metadata offset + +The peer sent an invalid metadata message + +The peer sent a peer exchange message that was too large + +The peer sent an invalid peer exchange message + +The peer sent an invalid tracker exchange message + +The peer sent an pex messages too often. This is a possible +attempt of and attack + +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. + +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. + +The peer tried to connect to an SSL torrent without connecting +over SSL. + +The peer tried to connect to a torrent with a certificate +for a different torrent. + +the torrent is not an SSL torrent, and the operation requires +an SSL torrent + +peer was banned because its listen port is within a banned port +range, as specified by the port_filter. + +The session_handle is not referring to a valid session_impl + +the listen socket associated with this request was closed + + + + + + + + + +The resume data file is missing the ``file sizes`` entry + +The resume data file ``file sizes`` entry is empty + +The resume data file is missing the ``pieces`` and ``slots`` entry + +The number of files in the resume data does not match the number +of files in the torrent + +One of the files on disk has a different size than in the fast +resume file + +One of the files on disk has a different timestamp than in the +fast resume file + +The resume data file is not a dictionary + +The ``blocks per piece`` entry is invalid in the resume data file + +The resume file is missing the ``slots`` entry, which is required +for torrents with compact allocation. *DEPRECATED* + +The resume file contains more slots than the torrent + +The ``slot`` entry is invalid in the resume data + +One index in the ``slot`` list is invalid + +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* + +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. + +The HTTP header was not correctly formatted + +The HTTP response was in the 300-399 range but lacked a location +header + +The HTTP response was encoded with gzip or deflate but +decompressing it failed + +The URL specified an i2p address, but no i2p router is configured + +i2p acceptor is not available yet, can't announce without endpoint + +The tracker URL doesn't support transforming it into a scrape +URL. i.e. it doesn't contain "announce. + +invalid tracker response + +invalid peer dictionary entry. Not a dictionary + +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 + +skipped announce (because it's assumed to be unreachable over the +given source network interface) + +random number generation failed + +blocked by SSRF mitigation + +blocked because IDNA host names are banned + +the torrent file has an unknown meta version + +the v2 torrent file has no file tree + +the torrent contains v2 keys but does not specify meta version 2 + +the v1 and v2 file metadata does not match + +one or more files are missing piece layer hashes + +a piece layer has the wrong size or failed hash check + +a v2 file entry has no root hash + +the v1 and v2 hashes do not describe the same data + +a file in the v2 metadata has the pad attribute set + +the number of error codes + +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. + + + + + + + + + + + + + + + + + + +HTTP errors are reported in the libtorrent::http_category, with error code enums in +the ``libtorrent::errors`` namespace. + +return the instance of the libtorrent_error_category which +maps libtorrent error codes to human readable error messages. + +returns the error_category for HTTP errors + +explicitly converts to true if this object represents an error, and +false if it does not. + +the error that occurred + +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. + +A code from operation_t enum, indicating what +kind of operation failed. + +used by storage to return errors +also includes which underlying file the +error happened on + +constructs a memory mapped file disk I/O object. + +creates a disk io object that discards all data written to it, and only +returns zero-buffers when read from. May be useful for testing and +benchmarking. + +See documentation of internal random_bytes + +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. + +Creates a signature of the given message with the given key pair. + +Verifies the signature on the given message using ``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 + +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 + + +This member function set the counters to zero. + +This structure hold the relevant counters for the storage + +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. + +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 settings_pack::dht_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. + +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. + +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. + +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 +settings_pack::dht_max_dht_items. + + +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. + +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. + +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 +settings_pack::dht_max_dht_items. + + +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. + +This function is called periodically (non-constant frequency). + +For implementers: +Use this functions for expire peers or items or any other +storage cleanup. + +return stats counters for the store + +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. + +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. + +announce to DHT as a seed + +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. + +Specify the port number for the SSL listen socket in the DHT announce. + +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. + + +the bootstrap nodes saved from the buckets node + +the bootstrap nodes saved from the IPv6 buckets node + + +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`_ + + +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(). + +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(). + +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/projects.rst b/docs/projects.rst new file mode 100644 index 0000000..3aa0d3d --- /dev/null +++ b/docs/projects.rst @@ -0,0 +1,209 @@ +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/ + +DownZemAll +---------- + +`DownZemAll!`_ is a mass download manager for Windows, Mac OS X and Linux. It helps +you to select, organize, prioritize and run your downloads in parallel. Based on +the Qt5 framework, DownZemAll! is written in C/C++. It's a free (as in "free +speech" and also as in "free beer") software. Its use is governed by LGPL +License. + +.. _`DownZemAll!`: https://setvisible.github.io/DownZemAll/ + +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.rst b/docs/python_binding.rst new file mode 100644 index 0000000..429444a --- /dev/null +++ b/docs/python_binding.rst @@ -0,0 +1,263 @@ +========================= +libtorrent python binding +========================= + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +building +======== + +libtorrent can be built as a python module. + +The best way to build the python bindings is using ``setup.py``. This invokes +``b2`` under the hood, so you must have all of libtorrent's build dependencies +installed. + +If you just want to build the shared library python extension without python +packaging semantics, you can also invoke ``b2`` directly. + +prerequisites +============= + +Whether building with ``setup.py`` or directly invoking ``b2``, you must +install the build prerequisites on your system: + +1. All `the build prerequisites for the main libtorrent library`__, including + boost libraries and ``b2``, and your building toolchain (``gcc``, visual + studio, etc). +2. Boost.Python, if not otherwise included in your boost installation +3. Python 3.6+. Older versions may work, but are not tested. + +.. __: building.html + +environment variables +--------------------- + +``b2`` is very sensitive to environment variables. At least the following are +required: + +1. ``BOOST_ROOT`` +2. ``BOOST_BUILD_PATH`` + +``b2`` is also known to reference dozens of other environment variables when +detecting toolsets. Keep this in mind if you are building in an isolation +environment like ``tox``. + +building with setup.py +====================== + +By default, ``setup.py`` will invoke ``b2`` to build libtorrent:: + + python setup.py build + +``setup.py`` is a normal ``distutils``-based setup script. + +To install into your python environment:: + + python setup.py install + +To build a binary wheel package:: + + python -m pip install wheel + python setup.py bdist_wheel + +build for a different python version +------------------------------------ + +``setup.py`` will target the running interpreter. To build for different python +versions, you must change how you invoke ``setup.py``:: + + # build for python3.6 + python3.6 setup.py build + # build for python3.7 + python3.7 setup.py build + + +customizing the build +--------------------- + +You can customize the build by passing options to the ``build_ext`` step of +``setup.py`` by passing arguments directly to ``b2`` via ``--b2-args=``:: + + python setup.py build_ext --b2-args="toolset=msvc-14.2 linkflags=-L../../src/.libs" + +For a full list of ``b2`` build options, see `libtorrent build features`_. + +.. _`libtorrent build features`: building.html#build-features + +Here, it's important to note that ``build_ext`` has no "memory" of the build +config and arguments you passed to it before. This is *different* from the way +``distutils`` normally works. Consider:: + + python setup.py build_ext --b2-args="optimization=space" + # the following will build with DEFAULT optimization + python setup.py install + +In order to customize the build *and* run other steps like installation, you +should run the steps inline with ``build_ext``:: + + python setup.py build_ext --b2-args="optimization=space" install + + +building with b2 +================ + +You will need to update your ``user-config.jam`` so ``b2`` can find your python +installation. + +``b2`` has some auto-detection capabilities. You may be able to do just this:: + + using python : 3.6 ; + +However you may need to specify full paths. On windows, it make look like +this:: + + using python : 3.6 : C:/Users//AppData/Local/Programs/Python/Python36 : C:/Users//AppData/Local/Programs/Python/Python36/include : C:/Users//AppData/Local/Programs/Python/Python36/libs ; + +Or on Linux, like this:: + + using python : 3.6 : /usr/bin/python3.6 : /usr/include/python3.6 : /usr/lib/python3.6 ; + +Note that ``b2``'s python path detection is known to only work for global +python installations. It is known to be broken for virtualenvs or ``pyenv``. If +you are using ``pyenv`` to manage your python versions, you must specify full +include and library paths yourself. + +invoking b2 +----------- + +Build the bindings like so:: + + cd bindings/python + b2 release python=3.6 address-model=64 + +Note that ``address-model`` should match the python installation you are +building for. + +For other build features, see `libtorrent build options`_. + +.. _`libtorrent build options`: building.html#build-features + + +static linking +-------------- + +A python module is a shared library. Specifying ``link=static`` when building +the binding won't work, as it would try to produce a static library. + +Instead, control whether the libtorrent main library or boost is linked +statically with ``libtorrent-link=static`` and ``boost-link=static`` +respectively. + +By default both are built and linked as shared libraries. + +Building and linking boost as static library is only possibly by building it +from source. Specify the ``BOOST_ROOT`` environment variable to point to the +root directory of the boost source distribution. + +For example, to build a self-contained python module:: + + b2 release python=3.6 libtorrent-link=static boost-link=static + +helper targets +-------------- + +There are some targets for placing the build artifact in a helpful location:: + + $ b2 release python=3.6 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``. + +To install the python module, build it with the following command:: + + b2 release python=3.6 install_module + +By default the module will be installed to the python user site. This can be +changed with the ``python-install-scope`` feature. The valid values are ``user`` +(default) and ``system``. e.g.:: + + b2 release python=3.6 install_module python-install-scope=system + +To specify a custom installation path for the python module, specify the desired +path with the ``python-install-path`` feature. e.g.:: + + b2 release python=3.6 install_module python-install-path=/home/foobar/python-site/ + +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/security-audit.rst b/docs/security-audit.rst new file mode 100644 index 0000000..24768af --- /dev/null +++ b/docs/security-audit.rst @@ -0,0 +1,491 @@ +============================ +Security audit of libtorrent +============================ + +:Author: Arvid Norberg, arvid@libtorrent.org + +In the 4th quarter of 2020 `Mozilla Open Source Support Awards`__ commissioned a +security audit of libtorrent, to be performed by `include security`_. + +__ https://www.mozilla.org/en-US/moss/ +.. _`include security`: https://includesecurity.com/ + +The full report from the audit can be found here_. + +.. _here: 2020\ Q4\ Mozilla\ Libtorrent\ Report\ Public\ Report.pdf + +This document discusses the issues raised by the report as well as describes the +changes made to libtorrent in response to it. These changes were included in +libtorrent version 1.2.12 and version 2.0.2. + +Comments on this document are welcome through any of these means: + +* email them to ``arvid@libtorrent.org`` +* email to libtorrent `mailing list`_ +* an issue on github_. + +.. _`mailing list`: https://sourceforge.net/projects/libtorrent/lists/libtorrent-discuss +.. _github: https://github.com/arvidn/libtorrent/issues + +.. contents:: issues brought up in the report + +F1: Server-Side Request Forgery (SSRF) +====================================== + +For background, see `OWASP definition of SSRF`__. + +__ https://owasp.org/www-community/attacks/Server_Side_Request_Forgery + +Running a tracker on the local network is an established use case for +BitTorrent (here__). Filtering all tracker requests to the local network is not feasible. +Running a tracker on the loopback device would seem to only make sense for +testing. + +__ https://github.com/AVBIT/retracker_local + +The SSRF issue is not limited to tracker URLs, but also applies to web seeds. A +web seed can be embedded in a .torrent file as well as included in a magnet +link. + +The report says: + + If user-controllable URLs must be requested then sanitizing them in a manner + similar to the SafeCurl library is recommended (see the link in the reference + section). + +The `SafeCurl library`__, as I understand is, sanitizes URLs based on include- +and exclude lists of host names, IP addresses, ports, schemes. + +__ https://github.com/wkcaj/safecurl/blob/master/src/fin1te/SafeCurl/Url.php + +tracker and web seed protocols +------------------------------ + +Tracker URLs can be arbitrary URLs that libtorrent appends certain query string +parameters to (like ``&info_hash=`` etc.). The path component of a tracker URL +is typically not relevant, and most trackers follow the convention of using +``/announce``. + +A web seed for a multi-file torrent cannot include any query string arguments +and libtorrent will append the path to the file that's being requested. However, +the response from the web seed can *redirect* to any arbitrary URL, including on +the local network. A web seed for a single-file torrent can be any arbitrary URL. + +Web seed HTTP requests will almost always be a range request (unless the file is +so small to fit in one or a few pieces). + +What heuristics and restrictions could libtorrent implement to mitigate attacks? + +Both trackers and web seeds only use HTTP ``GET`` request, i.e. no ``POST`` for +example. This ought to protect certain APIs that mutate state. + +The examples in the OWASP article are: + +Cloud server meta-data +---------------------- + + Cloud services such as AWS provide a REST interface on + ``http://169.254.169.254/`` where important configuration and sometimes even + authentication keys can be extracted + +The response from a REST API would have to be compatible with the +BitTorrent tracker protocol, which is a bencoded structure with specific keys +being mandatory (the protocol is defined here__, with amendments here__, +here__ and here__). + +__ https://www.bittorrent.org/beps/bep_0003.html#trackers +__ https://www.bittorrent.org/beps/bep_0023.html +__ https://www.bittorrent.org/beps/bep_0007.html +__ https://www.bittorrent.org/beps/bep_0048.html + +A tracker response that doesn't match this protocol will be ignored by libtorrent. +The response will not be published and made available anywhere, including the logs. +Therefore it's not likely there would be a way to *extract* data from a REST API +via a tracker request. + +Database HTTP interfaces +------------------------ + + NoSQL database such as MongoDB provide REST interfaces on HTTP ports. If the + database is expected to only be available to internally, authentication may + be disabled and the attacker can extract data + +Since libtorrent doesn't make the response from a tracker request available to +anybody, especially not if it's not a valid BitTorrent tracker response, it's +not likely data can be extracted via such tracker URL. See previous section for +details. + +Internal REST interfaces +------------------------ + +libtorrent can definitely hit a REST interface and may affect configuration +changes in other software that's installed on the local machine. This is +assuming that the software does not use any authentication other than checking +the source IP being the localhost. + +As mentioned earlier, extracting data from a REST API via a tracker URL is not +likely to be possible. + +It is established practice to include arbitrary URL query parameters in tracker +URLs, and clients amend them with the query parameters required by the tracker +protocol. This makes it difficult to sanitize the query string. + +One way to mitigate hitting REST APIs on local host is to require that tracker +URLs, for local host specifically, use the request path ``/announce``. This is +the convention for bittorrent trackers. + +Web seeds that resolve to a local network address are not allowed to have query +string parameters. + +This SSRF mitigation was implemented for trackers in `#5303`__ and for web seeds in `#5319`__. + +__ https://github.com/arvidn/libtorrent/pull/5303 +__ https://github.com/arvidn/libtorrent/pull/5319 + +Web Seeds that resolve to a *global* address (i.e. not loopback, local network +or multicast address) are not allowed to redirect to a non-global IP. This +mitigation was implemented in `#5846`__, for libtorrent-2.0.3. + +__ https://github.com/arvidn/libtorrent/pull/5846 + +Files +----- + + The attacker may be able to read files using ```` URIs + + +libtorrent only supports ``http``, ``https`` and ``udp`` protocol schemes, and +will reject any other tracker URL. Specifically, libtorrent does not support +the ``file://`` URL scheme. + +Additionally, `#5346`__ implements checks for tracker URLs that include query +string arguments that are supposed to be added by clients. + +__ https://github.com/arvidn/libtorrent/pull/5346 + +F2: Compile Options Can Remove Assert Security Validation +========================================================= + +The comments have been addressed in `#5308`__. The changes include: + +__ https://github.com/arvidn/libtorrent/pull/5308 + +* use ``span`` to simplify updates of pointer + length +* use ``span`` for (immutable) write buffers, to improve const + correctness and avoid a ``const_cast`` +* introduce additional sanity checks that no buffer lengths are < 0 +* introduce additional check to ensure buffer lengths fit in unsigned 16 bit + field (in the case where it's stored in one) +* generally reduce signed <-> unsigned casts + +F3: Confidential and Security Relevant Information Stored in Logs +================================================================= + +The secret keys for protocol encryption are not particularly sensitive, since +it's primarily an obfuscation feature. However, I have never had to use these +keys for debugging, so they don't have much value in the log anyway. + +Addressed in `#5299`__. + +__ https://github.com/arvidn/libtorrent/pull/5299 + +F4: Pseudo Random Number Generator Is Vulnerable to Prediction Attack +===================================================================== + +These are the places ``random_bytes()``, ``random()`` and ``random_shuffle()`` +are used in libtorrent. The "crypto" column indicates whether the random number +is sensitive and must be hard to predict, i.e. have high entropy. + +.. list-table:: + :widths: auto + :header-rows: 1 + + * - crypto + - Use + - Description + * - **Yes** + - PCP nonce + - generating a nonce for PCP (Port Control Protocol). The `PCP RFC section 11.2`__ + references `RFC 4086 Randomness Requirements for Security`__ for + the nonce generation. + + This was fixed. + + __ https://tools.ietf.org/html/rfc6887#section-11.2 + __ https://tools.ietf.org/html/rfc4086 + * - **Yes** + - DHT ed25519 keys + - used for kademlia mutable put feature. These keys are sensitive an + should use an appropriate entropy source. This is not done as part of + normal libtorrent operations, it's a utility function a client using the mutable + PUT-feature can call. This functionality is exposed in the + ``ed25519_create_seed()`` function. + + This was fixed. + * - **Maybe** + - DHT write-token + - The DHT maintains a secret 32 bit number which is updated every 5 + minutes to a new random number. The secret from the last 5 minute period + is also remembered. In responses to ``get`` and ``get_peers`` messages a + *write token* is generated and included. The write token is the first 32 + bits of a SHA-1 of the source IP address, the current secret and the + info_hash. ``put`` and ``announce_peer`` requests are ignored if the + write token is invalid given the current or the last secret. This is + like a SYN-cookie. + + This was changed to use cryptographic random numbers. + * - **Maybe** + - DHT transaction ID + - Each DHT request that is sent to a node includes a 16 bit transaction ID + that must be returned in the response. This is used to map responses to + the correct request (required when making multiple requests to the same + IP), but also to make it harder for a 3rd party to spoof the source IP + and fake a response. Presumably the fact that there are only 65536 + different transaction IDs would be a problem before someone guesses the + random number. Additionally, a request is only valid for a few tens of + seconds, which further mitigates spoofed responses. + + This has been left using pseudo random numbers. + * - **Maybe** + - uTP sequence numbers + - When connecting a uTP socket, the initial sequence number is chosen at + random. + + This has been left using pseudo random numbers. + * - No + - protocol encryption (obfuscation) + - both key generation for DH handshake as well as random + padding ahead of handshake. The protocol encryption feature + is not intended to provide any authentication or confidentiality. + * - No + - i2p session-id + - generation of the session ID, not key generation. All crypto, + including key generation is done by the i2p daemon implementing + the SAM bridge. + * - No + - DHT node-id + - The node ID does not need to be hard to guess, just uniformly + distributed. + * - No + - DHT node-id fingerprint + - Used to identify announces to fake info-hashes. More info here__. + + __ https://blog.libtorrent.org/2014/11/dht-routing-table-maintenance/ + * - No + - DHT peer storage + - When returning peers from peer storage, in response to a DHT + ``get_peers`` request, we pick *n* of *m* random peers. + * - No + - peer-id + - In bittorrent, each peer generates a random peer-id used in interactions + with other peers as well as HTTP(S) trackers. The peer-id is not secret + and does not need to be hard to guess. In fact, for each peer libtorrent + connects to, it generates a different peer-id. Additionally, each torrent + has a unique peer-id that's advertised to trackers. Trackers need a + consistent peer-id for its book keeping. + * - No + - ip_voter + - The ip_voter maintains a list of possible external IP addresses, based + on how many peer interactions we've seen telling us that's our external + IP as observed by them. Knowing our external IP is not critical, it's + primarily used to generate our DHT node ID according to this__. + + __ http://libtorrent.org/dht_sec.html + + The ip_voter uses ``random()`` to probabilistically drop a record of a + possible external IP, if there are too many. + * - No + - local service discovery + - In order to ignore our own service discovery messages sent on a + multi-cast group, we include a "cookie". If we see our own cookie, we + ignore the message. The cookie is generated by ``random()``. + * - No + - piece picker + - The order pieces are picked in is rarest first. Pieces of the same + rarity are picked in random order, using ``random()``. + * - No + - smart-ban + - If a piece fails the hash check, we may not know which peer sent the + corrupt data. The smart ban function will record the hashes of all blocks + of the failed piece. Once the piece passes, it can compare the passing + blocks against the failing one, identifying exactly which peer sent corrupt + data. This is a property of how bittorrent *checks* data at the piece + level, but downloads smaller parts (called "blocks") from potentially + different peers. + + In earlier version of libtorrent, the block hash would use CRC32, and a + secret salt to prevent trivial exploiting by malicious peers. This is no + longer the case, smart-ban uses SHA-1 now, so there is no need for the salt. + + It was removed in `#5295`__. + + __ https://github.com/arvidn/libtorrent/pull/5295 + * - No + - peer-list pruning + - When the peer list has too many peers in it, random low quality peers + are pruned. + * - No + - peer-list duplicate peer + - When receiving a connection from an IP we're already connected to, the + connection to keep and which one to disconnect is based on the local and + remote port numbers. If the ports are the same, one of the two connections + are closed randomly. + * - No + - UPnP external port + - When the external port of a mapping conflicts with an existing map, the + port mapping is re-attempted with a random external port. + * - No + - ut_metadata re-request timeout + - When a peer responds to a metadata request with "don't have", we delay + randomly between 20 - 70 seconds before re-requesting. + * - No + - web seeds + - Web seeds are shuffled, to attempt connecting to them in random order + * - No + - trackers + - Trackers within the same tier are shuffled, to try them in random order + (for load balancing) + * - No + - resume data peers + - When saving resume data and we have more than 100 peers, once "high + quality peers" have been saved, pick low quality peers at random to save. + * - No + - share mode seeds + - In share mode, where libtorrent attempts to maximize its upload to + download ratio, if we're connected to too many seeds, some random seeds + are disconnected. + * - No + - share mode pick + - In share mode, when more than one piece has the lowest availability, one + of them is picked at random + * - No + - http_connection endpoints + - After a successful hostname lookup, the endpoints are randomized to try + them in an arbitrary order, for load balancing. + * - No + - super seeding piece picking + - In Super seeding mode, the rarest piece is selected for upload. If + there's a tie, a piece is chosen at random. + * - No + - UDP listen socket + - When using a proxy, but not connecting peer via the proxy, the local UDP + socket, used for uTP and DHT traffic will bind to the listen socket of + the first configured listen interface. If there is no listen interface + configured, a random port is chosen. + * - No + - bind outgoing uTP socket + - When bind-outgoing-sockets is enabled, uTP sockets are bound to the + listen interface matching the target IP. If there is no match, an + interface is picked at random to bind the outgoing socket to. + * - No + - uTP send ID + - uTP connections are assigned send ID, to allow multiple connections to + the same IP. Similar to port number, but all uTP connections run over a + single UDP socket. + +The following issues were addressed: + +* the existing ``random_bytes()`` function was made to unconditionally produce + pseudo random bytes. +* increase amount of entropy to seed the pseudo random number generator. +* a new function ``crypto_random_bytes()`` was added which unconditionally + use a strong entropy source. +* If no specialized API is available for high-entropy random numbers is + available (like ``libcrypto`` or CryptoAPI on windows) random numbers are + pulled from ``/dev/urandom``. +* The PCP nonce was changed to use ``crypto_random_bytes()`` +* The ed25519 key seed function was changed to use ``crypto_random_bytes()`` + +Addressed in `#5298`__. + +__ https://github.com/arvidn/libtorrent/pull/5298 + +F5: Potential Null Pointer Dereference Issues +============================================= + +This was fundamentally caused by the boost.pool default allocator using ``new +(std::nothrow)``, rather than plain (throwing) ``new``. The code using the pool +added to the confusion by checking for a ``nullptr`` return value, but further +up the call chain that check was not made. The fix was to remove the check for +``nullptr`` and replace the boost.pool allocator to throw ``std::bad_alloc`` on +memory exhaustion. + +Addressed in `#5293`__. + +__ https://github.com/arvidn/libtorrent/pull/5293 + +F6: Integer Overflow +==================== + +This was a bug in the fuzzer itself, not in the production code (as far as I +could find). The parse_int fuzzer used an uninitialized variable. + +Addressed in `#5292`__. + +__ https://github.com/arvidn/libtorrent/pull/5292 + +F7: Magnet URIs Allow IDNA Domain Names +======================================= + +My understanding of this attack is that a tracker hostname could be crafted to +look like a well known host, but in fact be a different host, by using +look-alike unicode characters in the hostname. + +For example, the well-known tracker ``http://bt1.archive.org:6969/announce`` +could be spoofed by using ``bt1.archive.org`` (the ``e`` at the end is really +`U+ff45`__). + +__ https://unicode-table.com/en/FF45/ + +The issue of trusting trackers goes beyond tracker host names in magnet links. +Normal .torrent files also contain tracker URLs, and they could also use +misleading tracker host names. However, this highlights a more fundamental issue +that libtorrent does not provide an API for clients to vet trackers before +announcing to them. libtorrent provides an IP filter that will block announcing +to trackers, but not the URLs or host names directly. + +Having an ability to vet trackers before using them would also mitigate the +`F1: Server-Side Request Forgery (SSRF)`_. + +This issue also goes beyond trackers. Web seeds are also URLs embedded in +.torrent files or magnet links which libtorrent will make requests to. + +These are the changes I'm making to mitigate this issue: + +* enable ``validate_https_trackers`` by default. `#5314`__. The name of this + setting is misleading. It does not only affect trackers, but also web seeds. +* Support loading the system certificate store on windows, to authenticate + trackers with, `#5313`__. +* add an option to allow IDNA domain names, and disable it by default. This + applies to both trackers and web seeds. `#5316`__. + +__ https://github.com/arvidn/libtorrent/pull/5314 +__ https://github.com/arvidn/libtorrent/pull/5313 +__ https://github.com/arvidn/libtorrent/pull/5316 + + +I1: Additional Documentation and Automation +=========================================== + +Addressed in: + +* `#5337`__. + +__ https://github.com/arvidn/libtorrent/pull/5337 + +I2: Automated Fuzzer Generation +=============================== + +No effort has been put into generating fuzzers with FuzzGen_, but it's an +intriguing project I hope to have time to put some effort towards in the future. + +.. _FuzzGen: https://github.com/HexHive/FuzzGen + +I3: Type Confusion and Integer Overflow Improvements +==================================================== + +Addressed in: + +* `#5308`__. + +__ https://github.com/arvidn/libtorrent/pull/5308 diff --git a/docs/settings-ref.rst b/docs/settings-ref.rst new file mode 100644 index 0000000..9e0c271 --- /dev/null +++ b/docs/settings-ref.rst @@ -0,0 +1,3430 @@ +.. _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. + +When outgoing interfaces are specified, incoming connections or +packets sent to a local interface or IP that's *not* in this list +will be rejected with a `peer_blocked_alert`__ with +``invalid_local_interface`` as the reason. + +Note that these are just interface/adapter names or IP addresses. +There are no ports specified in this list. IPv6 addresses without +port should be specified without enclosing ``[``, ``]``. + +.. _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 | -LT2060- | ++------------------+--------+----------+ + +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. + +.. _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. + +.. _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 | ++---------------------+------+---------+ + +Enables incoming and outgoing, TCP and uTP peer connections. +``false`` is disabled and ``true`` is enabled. When outgoing +connections are disabled, libtorrent will simply not make +outgoing peer connections with the specific transport protocol. +Disabled incoming peer connections will simply be rejected. +These options only apply to peer connections, not tracker- or any +other kinds of connections. + +.. _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. + +* A generic user-agent will be + used for trackers (except for private torrents). +* Your local IPv4 and IPv6 address won't be sent as query string + parameters to private trackers. +* If announce_ip is configured, it will not be sent to trackers +* The client version will not be sent to peers in the extension + handshake. + +.. _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 + +.. _support_share_mode: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| support_share_mode | bool | true | ++--------------------+------+---------+ + +if false, prevents libtorrent to advertise share-mode support + +.. _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. + +.. _dht_restrict_routing_ips: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| dht_restrict_routing_ips | bool | true | ++--------------------------+------+---------+ + +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 + +.. _dht_restrict_search_ips: + +.. raw:: html + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| dht_restrict_search_ips | bool | 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. + +.. _dht_extended_routing_table: + +.. raw:: html + + + ++----------------------------+------+---------+ +| name | type | default | ++============================+======+=========+ +| dht_extended_routing_table | bool | 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. + +.. _dht_aggressive_lookups: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| dht_aggressive_lookups | bool | 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. + +.. _dht_privacy_lookups: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| dht_privacy_lookups | bool | false | ++---------------------+------+---------+ + +when set, perform lookups in a way that is slightly more expensive, +but which minimizes the amount of information leaked about you. + +.. _dht_enforce_node_id: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| dht_enforce_node_id | bool | 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". + +.. _dht_ignore_dark_internet: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| dht_ignore_dark_internet | bool | true | ++--------------------------+------+---------+ + +ignore DHT messages from parts of the internet we wouldn't expect to +see any traffic from + +.. _dht_read_only: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| dht_read_only | bool | false | ++---------------+------+---------+ + +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. + +.. _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 | true | ++-------------------------+------+---------+ + +when set to true, the certificate of HTTPS trackers and HTTPS web +seeds will be validated against the system's certificate store +(as defined by OpenSSL). If the system does not have a +certificate store, this option may have to be disabled in order +to get trackers and web seeds to work). + +.. _ssrf_mitigation: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| ssrf_mitigation | bool | true | ++-----------------+------+---------+ + +when enabled, tracker and web seed requests are subject to +certain restrictions. + +An HTTP(s) tracker requests to localhost (loopback) +must have the request path start with "/announce". This is the +conventional bittorrent tracker request. Any other HTTP(S) +tracker request to loopback will be rejected. This applies to +trackers that redirect to loopback as well. + +Web seeds that end up on the client's local network (i.e. in a +private IP address range) may not include query string arguments. +This applies to web seeds redirecting to the local network as +well. + +Web seeds on global IPs (i.e. not local network) may not redirect +to a local network address + +.. _allow_idna: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| allow_idna | bool | false | ++------------+------+---------+ + +when disabled, any tracker or web seed with an IDNA hostname +(internationalized domain name) is ignored. This is a security +precaution to avoid various unicode encoding attacks that might +happen at the application level. + +.. _enable_set_file_valid_data: + +.. raw:: html + + + ++----------------------------+------+---------+ +| name | type | default | ++============================+======+=========+ +| enable_set_file_valid_data | bool | false | ++----------------------------+------+---------+ + +when set to true, enables the attempt to use SetFileValidData() +to pre-allocate disk space. This system call will only work when +running with Administrator privileges on Windows, and so this +setting is only relevant in that scenario. Using +SetFileValidData() poses a security risk, as it may reveal +previously deleted information from the disk. + +.. _socks5_udp_send_local_ep: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| socks5_udp_send_local_ep | bool | false | ++--------------------------+------+---------+ + +When using a SOCKS5 proxy, UDP traffic is routed through the +proxy by sending a UDP ASSOCIATE command. If this option is true, +the UDP ASSOCIATE command will include the IP address and +listen port to the local UDP socket. This indicates to the proxy +which source endpoint to expect our packets from. The benefit is +that incoming packets can be forwarded correctly, before any +outgoing packets are sent. The risk is that if there's a NAT +between the client and the proxy, the IP address specified in the +protocol may not be valid from the proxy's point of view. + +.. _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 | 2000 | ++------------------------------+------+---------+ + +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. + +.. _disk_io_write_mode: + +.. _disk_io_read_mode: + +.. raw:: html + + + + ++--------------------+------+--------------------------------+ +| name | type | default | ++====================+======+================================+ +| disk_io_write_mode | int | DISK_WRITE_MODE | ++--------------------+------+--------------------------------+ +| 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. +write_through + flush pieces to disk as they complete validation. + +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 | 0x04 | ++----------+------+---------+ + +``peer_tos`` determines the TOS byte set in the IP header of every +packet sent to peers (including web seeds). ``0x0`` means no marking, +``0x04`` represents Lower Effort. For more details see `RFC 8622`_. + +.. _`RFC 8622`: http://www.faqs.org/rfcs/rfc8622.html + +.. _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. + +.. _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 | ++-----------------------+------+---------+ + +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. + +.. _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 | 2000 | ++------------------+------+---------+ + +``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. + +.. _hashing_threads: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| hashing_threads | int | 2 | ++-----------------+------+---------+ + +``hashing_threads`` is the number of disk I/O threads to use for +piece hash verification. These threads are *in addition* to the +regular disk I/O threads specified by `settings_pack::aio_threads`__. +The `hasher`__ threads do not only compute hashes, but also perform +the read from disk. On storage optimal for sequential access, +such as hard drives, this setting should probably be set to 1. + +.. _checking_mem_usage: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| checking_mem_usage | int | 256 | ++--------------------+------+---------+ + +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 | 10 | ++-------------+------+---------+ + +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 + +.. _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 240 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. + +.. _dht_max_peers_reply: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| dht_max_peers_reply | int | 100 | ++---------------------+------+---------+ + +the maximum number of peers to send in a reply to ``get_peers`` + +.. _dht_search_branching: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| dht_search_branching | int | 5 | ++----------------------+------+---------+ + +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 + +.. _dht_max_fail_count: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| dht_max_fail_count | int | 20 | ++--------------------+------+---------+ + +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. + +.. _dht_max_torrents: + +.. raw:: html + + + ++------------------+------+---------+ +| name | type | default | ++==================+======+=========+ +| dht_max_torrents | int | 2000 | ++------------------+------+---------+ + +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. + +.. _dht_max_dht_items: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| dht_max_dht_items | int | 700 | ++-------------------+------+---------+ + +max number of items the DHT will store + +.. _dht_max_peers: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| dht_max_peers | int | 500 | ++---------------+------+---------+ + +the max number of peers to store per torrent (for the DHT) + +.. _dht_max_torrent_search_reply: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| dht_max_torrent_search_reply | int | 20 | ++------------------------------+------+---------+ + +the max number of torrents to return in a torrent search query to the +DHT + +.. _dht_block_timeout: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| dht_block_timeout | int | 5 * 60 | ++-------------------+------+---------+ + +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. + +.. _dht_block_ratelimit: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| dht_block_ratelimit | int | 5 | ++---------------------+------+---------+ + +the max number of packets per second a DHT node is allowed to send +without getting banned. + +.. _dht_item_lifetime: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| dht_item_lifetime | int | 0 | ++-------------------+------+---------+ + +the number of seconds a immutable/mutable item will be expired. +default is 0, means never expires. + +.. _dht_sample_infohashes_interval: + +.. raw:: html + + + ++--------------------------------+------+---------+ +| name | type | default | ++================================+======+=========+ +| dht_sample_infohashes_interval | int | 21600 | ++--------------------------------+------+---------+ + +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). + +.. _dht_max_infohashes_sample_count: + +.. raw:: html + + + ++---------------------------------+------+---------+ +| name | type | default | ++=================================+======+=========+ +| dht_max_infohashes_sample_count | int | 20 | ++---------------------------------+------+---------+ + +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 + +.. _max_piece_count: + +.. raw:: html + + + ++-----------------+------+----------+ +| name | type | default | ++=================+======+==========+ +| max_piece_count | int | 0x200000 | ++-----------------+------+----------+ + +``max_piece_count`` is the maximum allowed number of pieces in +metadata received via magnet links. Loading large torrents (with +more pieces than the default limit) may also require passing in +a higher limit to `read_resume_data()`__ and +`torrent_info::parse_info_section()`__, if those are used. + +.. _metadata_token_limit: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| metadata_token_limit | int | 2500000 | ++----------------------+------+---------+ + +when receiving metadata (torrent file) from peers, this is the +max number of bencoded tokens we're willing to parse. This limit +is meant to prevent DoS attacks on peers. For very large +torrents, this limit may have to be raised. + + +__ reference-Alerts.html#peer_blocked_alert +__ reference-Alerts.html#listen_failed_alert +__ reference-Alerts.html#listen_succeeded_alert +__ reference-Settings.html#outgoing_interfaces +__ reference-Settings.html#listen_interfaces +__ reference-Settings.html#generate_fingerprint() +__ reference-Alerts.html#portmap_alert +__ reference-Alerts.html#portmap_error_alert +__ manual-ref.html#upnp-and-nat-pmp +__ reference-Alerts.html#portmap_alert +__ reference-Alerts.html#portmap_error_alert +__ manual-ref.html#upnp-and-nat-pmp +__ reference-Bencoding.html#entry +__ reference-Session.html#session +__ reference-Settings.html#choking_algorithm_t +__ reference-Settings.html#seed_choking_algorithm_t +__ manual-ref.html#queuing +__ manual-ref.html#peer-classes +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Alerts.html#alert +__ reference-Settings.html#aio_threads +__ reference-Utility.html#hasher +__ manual-ref.html#queuing +__ reference-Alerts.html#alert_category_t +__ reference-Settings.html#enc_policy +__ reference-Settings.html#enc_level +__ reference-Settings.html#proxy_type_t +__ reference-Resume_Data.html#read_resume_data() +__ reference-Torrent_Info.html#parse_info_section() + diff --git a/docs/single-page-ref.rst b/docs/single-page-ref.rst new file mode 100644 index 0000000..f71d467 --- /dev/null +++ b/docs/single-page-ref.rst @@ -0,0 +1,29321 @@ +.. include:: header.rst + +`home`__ + +__ reference.html + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +bdecode_node +------------ + +Declared in "`libtorrent/bdecode.hpp`__" + + +__ include/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`__. + + +__ reference-Bdecoding.html#non_owning() +__ reference-Torrent_Info.html#type_t + + +.. parsed-literal:: + + + 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; + std::ptrdiff_t **data_offset** () const noexcept; + span **data_section** () const noexcept; + 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; + 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; + std::pair **dict_at_node** (int i) const; + std::int64_t **dict_find_int_value** (string\_view key + , std\:\:int64\_t default\_val = 0) const; + bdecode_node **dict_find_int** (string\_view key) const; + bdecode_node **dict_find** (string\_view key) const; + string_view **dict_find_string_value** (string\_view key + , string\_view default\_value = string\_view()) const; + int **dict_size** () const; + std::pair **dict_at** (int i) const; + std::int64_t **int_value** () const; + int **string_length** () const; + std::ptrdiff_t **string_offset** () const; + char const* **string_ptr** () 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 error) const; + + enum type_t + { + none_t, + dict_t, + list_t, + string_t, + int_t, + }; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +bdecode_node() +.............. + +.. parsed-literal:: + + **bdecode_node** () = default; + + +creates a default constructed node, it will have the type ``none_t``. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +operator=() bdecode_node() +.......................... + +.. parsed-literal:: + + **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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +type() +...... + +.. parsed-literal:: + + type_t **type** () const noexcept; + + +the type of this node. See `type_t`__. + + +__ reference-Torrent_Info.html#type_t + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +bool() +...... + +.. parsed-literal:: + + explicit operator **bool** () const noexcept; + + +returns true if `type()`__ != none_t. + + +__ reference-Plugins.html#type() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +non_owning() +............ + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +data_offset() data_section() +............................ + +.. parsed-literal:: + + std::ptrdiff_t **data_offset** () const noexcept; + span **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. +the ``data_offset()`` function returns the byte-offset to this node in, +starting from the beginning of the buffer that was parsed. + + + +.. raw:: html + + + + + + +.. raw:: html + + [report issue] + + + +list_at() list_int_value_at() list_string_value_at() list_size() +................................................................ + +.. parsed-literal:: + + 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 ``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. + + + +.. raw:: html + + + + + + + + + + + + +.. raw:: html + + [report issue] + + + +dict_at() dict_find_list() dict_find_dict() dict_find_string_value() dict_size() dict_find_int_value() dict_at_node() dict_find_string() dict_find_int() dict_find() +.................................................................................................................................................................... + +.. parsed-literal:: + + 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; + std::pair **dict_at_node** (int i) const; + std::int64_t **dict_find_int_value** (string\_view key + , std\:\:int64\_t default\_val = 0) const; + bdecode_node **dict_find_int** (string\_view key) const; + bdecode_node **dict_find** (string\_view key) const; + string_view **dict_find_string_value** (string\_view key + , string\_view default\_value = string\_view()) const; + int **dict_size** () const; + std::pair **dict_at** (int i) 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 ``string_view`` 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). + +``dict_at()`` returns the (key, value)-pair at the specified index in a +dictionary. Keys are only allowed to be strings. ``dict_at_node()`` also +returns the (key, value)-pair, but the key is returned as a +``bdecode_node`` (and it will always be a string). + + +__ reference-Bdecoding.html#bdecode_node + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +int_value() +........... + +.. parsed-literal:: + + std::int64_t **int_value** () const; + + +this function is only valid if ``type()`` == ``int_t``. It returns the +value of the integer. + + + +.. raw:: html + + + + + + +.. raw:: html + + [report issue] + + + +string_ptr() string_value() string_length() string_offset() +........................................................... + +.. parsed-literal:: + + int **string_length** () const; + std::ptrdiff_t **string_offset** () const; + char const* **string_ptr** () 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. +``string_offset()`` returns the byte offset from the start of the parsed +bencoded buffer this string can be found. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +clear() +....... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +swap() +...... + +.. parsed-literal:: + + void **swap** (bdecode\_node& n); + + +Swap contents. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +reserve() +......... + +.. parsed-literal:: + + 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()`__. + + +__ reference-Bdecoding.html#bdecode() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +switch_underlying_buffer() +.......................... + +.. parsed-literal:: + + 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(). + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +has_soft_error() +................ + +.. parsed-literal:: + + bool **has_soft_error** (span error) const; + + +returns true if there is a non-fatal error in the bencoding of this node +or its children + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum type_t +........... + +Declared in "`libtorrent/bdecode.hpp`__" + + +__ include/libtorrent/bdecode.hpp + ++----------+-------+------------------------------------------------------------+ +| name | value | description | ++==========+=======+============================================================+ +| none_t | 0 | uninitialized or default constructed. This is also used | +| | | to indicate that a node was not found in some cases. | +| | | | ++----------+-------+------------------------------------------------------------+ +| dict_t | 1 | a dictionary node. The ``dict_find_`` functions are valid. | +| | | | ++----------+-------+------------------------------------------------------------+ +| list_t | 2 | a list node. The ``list_`` functions are valid. | +| | | | ++----------+-------+------------------------------------------------------------+ +| string_t | 3 | a string node, the ``string_`` functions are valid. | +| | | | ++----------+-------+------------------------------------------------------------+ +| int_t | 4 | an integer node. The ``int_`` functions are valid. | +| | | | ++----------+-------+------------------------------------------------------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +print_entry() +------------- + +Declared in "`libtorrent/bdecode.hpp`__" + + +__ include/libtorrent/bdecode.hpp + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +bdecode() +--------- + +Declared in "`libtorrent/bdecode.hpp`__" + + +__ include/libtorrent/bdecode.hpp + +.. parsed-literal:: + + bdecode_node **bdecode** (span buffer + , error\_code& ec, int\* error\_pos = nullptr, int depth\_limit = 100 + , int token\_limit = 2000000); + 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 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. + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +session_handle +-------------- + +Declared in "`libtorrent/session_handle.hpp`__" + + +__ include/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. + + +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Torrent_Info.html#is_valid() + + +.. parsed-literal:: + + + struct session_handle + { + bool **is_valid** () const; + session_params **session_state** (save\_state\_flags\_t flags = save\_state\_flags\_t\:\:all()) const; + void **refresh_torrent_status** (std\:\:vector\* ret + , status\_flags\_t flags = {}) const; + std::vector **get_torrent_status** ( + std\:\:function const& pred + , 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** (); + void **set_dht_state** (dht\:\:dht\_state const& st); + void **set_dht_state** (dht\:\:dht\_state&& st); + std::vector **get_torrents** () const; + torrent_handle **find_torrent** (sha1\_hash const& info\_hash) const; + torrent_handle **add_torrent** (add\_torrent\_params&& params, error\_code& ec); + void **async_add_torrent** (add\_torrent\_params const& params); + torrent_handle **add_torrent** (add\_torrent\_params const& params, error\_code& ec); + torrent_handle **add_torrent** (add\_torrent\_params const& params); + torrent_handle **add_torrent** (add\_torrent\_params&& params); + void **async_add_torrent** (add\_torrent\_params&& params); + bool **is_paused** () const; + void **resume** (); + void **pause** (); + bool **is_dht_running** () const; + void **set_dht_storage** (dht\:\:dht\_storage\_constructor\_type sc); + void **add_dht_node** (std\:\:pair const& node); + void **dht_get_item** (sha1\_hash const& target); + void **dht_get_item** (std\:\:array key + , std\:\:string salt = std\:\:string()); + sha1_hash **dht_put_item** (entry data); + void **dht_put_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 const& e, client\_data\_t userdata = {}); + void **add_extension** (std\:\:function( + torrent\_handle const&, client\_data\_t)> ext); + void **add_extension** (std\:\:shared\_ptr ext); + ip_filter **get_ip_filter** () const; + void **set_ip_filter** (ip\_filter f); + 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); + peer_class_info **get_peer_class** (peer\_class\_t cid) const; + void **set_peer_class** (peer\_class\_t cid, peer\_class\_info const& pci); + void **remove_torrent** (const torrent\_handle&, remove\_flags\_t = {}); + settings_pack **get_settings** () const; + void **apply_settings** (settings\_pack&&); + void **apply_settings** (settings\_pack const&); + void **set_alert_notify** (std\:\:function const& fun); + alert\* **wait_for_alert** (time\_duration max\_wait); + void **pop_alerts** (std\:\:vector\* alerts); + std::vector **add_port_mapping** (portmap\_protocol t, int external\_port, int local\_port); + void **delete_port_mapping** (port\_mapping\_t handle); + void **reopen_network_sockets** (reopen\_network\_flags\_t options = reopen\_map\_ports); + std::shared_ptr **native_handle** () const; + + static constexpr save_state_flags_t **save_settings** = 0_bit; + static constexpr save_state_flags_t **save_dht_state** = 2_bit; + static constexpr save_state_flags_t **save_extension_state** = 11_bit; + static constexpr save_state_flags_t **save_ip_filter** = 12_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 **paused** = 2_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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +is_valid() +.......... + +.. parsed-literal:: + + 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. + + +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session_handle + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +session_state() +............... + +.. parsed-literal:: + + session_params **session_state** (save\_state\_flags\_t flags = save\_state\_flags\_t\:\:all()) const; + + +returns the current `session`__ state. This can be passed to +`write_session_params()`__ to save the state to disk and restored using +`read_session_params()`__ when constructing a new `session`__. The kind of +state that's included is all settings, the DHT routing table, possibly +plugin-specific state. +the flags parameter can be used to only save certain parts of the +`session`__ state + + +__ reference-Session.html#session +__ reference-Session.html#write_session_params() +__ reference-Session.html#read_session_params() +__ reference-Session.html#session +__ reference-Session.html#session + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +refresh_torrent_status() get_torrent_status() +............................................. + +.. parsed-literal:: + + void **refresh_torrent_status** (std\:\:vector\* ret + , status\_flags\_t flags = {}) const; + std::vector **get_torrent_status** ( + std\:\:function const& pred + , 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. + + +__ reference-Torrent_Status.html#torrent_status +__ reference-Torrent_Handle.html#status() +__ reference-Torrent_Status.html#torrent_status +__ reference-Session.html#get_torrent_status() +__ reference-Torrent_Status.html#torrent_status + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +post_torrent_updates() +...................... + +.. parsed-literal:: + + 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`__. + + +__ reference-Session.html#session +__ reference-Alerts.html#state_update_alert +__ reference-Add_Torrent.html#add_torrent_params +__ reference-Torrent_Handle.html#status() +__ reference-Torrent_Handle.html#torrent_handle + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +post_session_stats() +.................... + +.. parsed-literal:: + + 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. + + +__ reference-Alerts.html#session_stats_alert +__ reference-Stats.html#counters +__ reference-Stats.html#counters +__ reference-Session.html#session +__ reference-Stats.html#session_stats_metrics() +__ manual-ref.html#session-statistics + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +post_dht_stats() +................ + +.. parsed-literal:: + + void **post_dht_stats** (); + + +This will cause a `dht_stats_alert`__ to be posted. + + +__ reference-Alerts.html#dht_stats_alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +set_dht_state() +............... + +.. parsed-literal:: + + void **set_dht_state** (dht\:\:dht\_state const& st); + void **set_dht_state** (dht\:\:dht\_state&& st); + + +set the DHT state for the `session`__. This will be taken into account the +next time the DHT is started, as if it had been passed in via the +`session_params`__ on startup. + + +__ reference-Session.html#session +__ reference-Session.html#session_params + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +get_torrents() find_torrent() +............................. + +.. parsed-literal:: + + std::vector **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`__. + + +__ reference-Session.html#session +__ reference-Torrent_Handle.html#torrent_handle +__ reference-Torrent_Handle.html#torrent_handle +__ reference-Session.html#session + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +async_add_torrent() add_torrent() +................................. + +.. parsed-literal:: + + torrent_handle **add_torrent** (add\_torrent\_params&& params, error\_code& ec); + void **async_add_torrent** (add\_torrent\_params const& params); + torrent_handle **add_torrent** (add\_torrent\_params const& params, error\_code& ec); + torrent_handle **add_torrent** (add\_torrent\_params const& params); + torrent_handle **add_torrent** (add\_torrent\_params&& params); + void **async_add_torrent** (add\_torrent\_params&& params); + + +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. + + +__ reference-Session.html#add_torrent() +__ reference-Session.html#add_torrent() +__ reference-Torrent_Handle.html#torrent_handle +__ reference-Session.html#async_add_torrent() +__ reference-Alerts.html#add_torrent_alert +__ reference-Torrent_Handle.html#torrent_handle +__ reference-Session.html#add_torrent() +__ reference-Session.html#session +__ reference-Session.html#add_torrent() +__ reference-Add_Torrent.html#add_torrent_params + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +pause() resume() is_paused() +............................ + +.. parsed-literal:: + + bool **is_paused** () const; + void **resume** (); + 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. + + +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +is_dht_running() +................ + +.. parsed-literal:: + + bool **is_dht_running** () const; + + +``is_dht_running()`` returns true if the DHT support has been started +and false otherwise. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +set_dht_storage() +................. + +.. parsed-literal:: + + 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. + + +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +add_dht_node() +.............. + +.. parsed-literal:: + + void **add_dht_node** (std\:\:pair 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_get_item() +.............. + +.. parsed-literal:: + + 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`__. + + +__ reference-Alerts.html#dht_immutable_item_alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_get_item() +.............. + +.. parsed-literal:: + + void **dht_get_item** (std\:\:array 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. + + +__ reference-Alerts.html#dht_mutable_item_alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_put_item() +.............. + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_put_item() +.............. + +.. parsed-literal:: + + void **dht_put_item** (std\:\:array key + , std\:\:function& + , 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& 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. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +dht_get_peers() dht_announce() +.............................. + +.. parsed-literal:: + + 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 = {}); + + +``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`__. + + +__ reference-Alerts.html#dht_get_peers_reply_alert +__ reference-DHT.html#announce_flags_t + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_live_nodes() +................ + +.. parsed-literal:: + + 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. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_sample_infohashes() +....................... + +.. parsed-literal:: + + 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``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_direct_request() +.................... + +.. parsed-literal:: + + void **dht_direct_request** (udp\:\:endpoint const& ep, entry const& e, client\_data\_t userdata = {}); + + +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. + + +__ reference-Alerts.html#dht_direct_response_alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +add_extension() +............... + +.. parsed-literal:: + + void **add_extension** (std\:\:function( + torrent\_handle const&, client\_data\_t)> ext); + void **add_extension** (std\:\:shared\_ptr 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``. 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`: reference-Plugins.html + + +__ reference-Session.html#session +__ reference-Plugins.html#plugin +__ reference-Plugins.html#plugin + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +get_ip_filter() set_ip_filter() +............................... + +.. parsed-literal:: + + ip_filter **get_ip_filter** () const; + void **set_ip_filter** (ip\_filter f); + + +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`__. + + +__ reference-Filter.html#ip_filter +__ reference-Alerts.html#peer_blocked_alert +__ reference-Filter.html#ip_filter +__ reference-Session.html#session +__ reference-Filter.html#ip_filter + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +set_port_filter() +................. + +.. parsed-literal:: + + 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. + + +__ reference-Filter.html#port_filter + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +is_listening() listen_port() ssl_listen_port() +.............................................. + +.. parsed-literal:: + + 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. + + +__ reference-Session.html#session +__ reference-Settings.html#listen_interfaces + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +set_peer_class_filter() get_peer_class_filter() +............................................... + +.. parsed-literal:: + + 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: + +.. 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`__. + + +__ reference-Session.html#session +__ reference-Filter.html#ip_filter +__ manual-ref.html#peer-classes + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +set_peer_class_type_filter() get_peer_class_type_filter() +......................................................... + +.. parsed-literal:: + + 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. peer-class type filter, removing classes +3. peer-class type filter, adding classes + +For more information, see `peer classes`__. + + +__ manual-ref.html#peer-classes + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +create_peer_class() +................... + +.. parsed-literal:: + + 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`__. + + +__ manual-ref.html#peer-classes +__ reference-Session.html#set_peer_class_filter() +__ manual-ref.html#peer-classes + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +delete_peer_class() +................... + +.. parsed-literal:: + + 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`__. + + +__ reference-Session.html#session +__ manual-ref.html#peer-classes + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +get_peer_class() set_peer_class() +................................. + +.. parsed-literal:: + + peer_class_info **get_peer_class** (peer\_class\_t cid) const; + void **set_peer_class** (peer\_class\_t cid, peer\_class\_info const& pci); + + +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`__. + + +__ reference-PeerClass.html#peer_class_info +__ manual-ref.html#peer-classes + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +remove_torrent() +................ + +.. parsed-literal:: + + void **remove_torrent** (const torrent\_handle&, remove\_flags\_t = {}); + + +``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`__. + +`remove_torrent()`__ is non-blocking, but will remove the torrent from the +`session`__ synchronously. Calling `session_handle::add_torrent()`__ immediately +afterward with the same torrent will succeed. Note that this creates a +new handle which is not equal to the removed one. + +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``. Once the torrent is deleted, a +`torrent_deleted_alert`__ is posted. + +The `torrent_handle`__ remains valid for some time after `remove_torrent()`__ is +called. It will become invalid only after all libtorrent tasks (such as +I/O tasks) release their references to the torrent. Until this happens, +`torrent_handle::is_valid()`__ will return true, and other calls such +as `torrent_handle::status()`__ will succeed. Because of this, and because +`remove_torrent()`__ is non-blocking, the following sequence usually +succeeds (does not throw system_error): +.. code:: c++ + + session.remove_handle(handle); + handle.save_resume_data(); + +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. + + +__ reference-Alerts.html#torrent_removed_alert +__ reference-Custom_Storage.html#remove_torrent() +__ reference-Session.html#session +__ reference-Session.html#add_torrent() +__ reference-Alerts.html#torrent_deleted_alert +__ reference-Torrent_Handle.html#torrent_handle +__ reference-Custom_Storage.html#remove_torrent() +__ reference-Torrent_Handle.html#is_valid() +__ reference-Torrent_Handle.html#status() +__ reference-Custom_Storage.html#remove_torrent() + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +get_settings() apply_settings() +............................... + +.. parsed-literal:: + + settings_pack **get_settings** () const; + void **apply_settings** (settings\_pack&&); + void **apply_settings** (settings\_pack const&); + + +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. + + +__ reference-Settings.html#settings_pack + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +pop_alerts() set_alert_notify() wait_for_alert() +................................................ + +.. parsed-literal:: + + void **set_alert_notify** (std\:\:function const& fun); + alert\* **wait_for_alert** (time\_duration max\_wait); + void **pop_alerts** (std\:\:vector\* alerts); + + +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. + + +__ reference-Session.html#session +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Alerts.html#alert +__ reference-Alerts.html#save_resume_data_alert +__ reference-Alerts.html#save_resume_data_failed_alert +__ reference-Alerts.html#alert +__ reference-Settings.html#alert_mask +__ reference-Alerts.html#alert +__ reference-Alerts.html#alerts_dropped_alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +delete_port_mapping() add_port_mapping() +........................................ + +.. parsed-literal:: + + std::vector **add_port_mapping** (portmap\_protocol t, int external\_port, int local\_port); + void **delete_port_mapping** (port\_mapping\_t handle); + + +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. + + +__ reference-Session.html#session +__ reference-Session.html#delete_port_mapping() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +reopen_network_sockets() +........................ + +.. parsed-literal:: + + 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. + + +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +native_handle() +............... + +.. parsed-literal:: + + std::shared_ptr **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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +save_settings + saves settings (i.e. the `settings_pack`__) + + +__ reference-Settings.html#settings_pack + +.. raw:: html + + + +.. raw:: html + + [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. + + +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +save_extension_state + load or save state from plugins + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +save_ip_filter + load or save the IP filter set on the `session`__ + + +__ reference-Session.html#session + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + +global_peer_class_id tcp_peer_class_id local_peer_class_id + built-in peer classes + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +delete_files + delete the files belonging to the torrent from disk. + including the part-file, if there is one + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +delete_partfile + delete just the part-file associated with this torrent + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +paused + when set, the `session`__ will start paused. Call + `session_handle::resume()`__ to start + + +__ reference-Session.html#session +__ reference-Session.html#resume() + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + +udp tcp + protocols used by `add_port_mapping()`__ + + +__ reference-Session.html#add_port_mapping() + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +session_proxy +------------- + +Declared in "`libtorrent/session.hpp`__" + + +__ include/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. + + +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session_proxy +__ reference-Session.html#session + + +.. parsed-literal:: + + + struct session_proxy + { + session_proxy& **operator=** (session\_proxy&&) & noexcept; + session_proxy& **operator=** (session\_proxy const&) &; + **session_proxy** (session\_proxy&&) noexcept; + **session_proxy** (session\_proxy const&); + **session_proxy** (); + **~session_proxy** (); + }; + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +operator=() session_proxy() ~session_proxy() +............................................ + +.. parsed-literal:: + + session_proxy& **operator=** (session\_proxy&&) & noexcept; + session_proxy& **operator=** (session\_proxy const&) &; + **session_proxy** (session\_proxy&&) noexcept; + **session_proxy** (session\_proxy const&); + **session_proxy** (); + **~session_proxy** (); + + +default constructor, does not refer to any `session`__ +implementation object. + + +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +session +------- + +Declared in "`libtorrent/session.hpp`__" + + +__ include/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()`__. + + +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Settings.html#settings_pack +__ reference-Session.html#apply_settings() + + +.. parsed-literal:: + + + struct session : session_handle + { + explicit **session** (session\_params&& params); + **session** (session\_params&& params, session\_flags\_t flags); + explicit **session** (session\_params const& params); + **session** (); + **session** (session\_params const& params, session\_flags\_t flags); + **session** (session\_params&& params, io\_context& ios, session\_flags\_t); + **session** (session\_params const& params, io\_context& ios); + **session** (session\_params&& params, io\_context& ios); + **session** (session\_params const& params, io\_context& ios, session\_flags\_t); + **~session** (); + session_proxy **abort** (); + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +session() +......... + +.. parsed-literal:: + + explicit **session** (session\_params&& params); + **session** (session\_params&& params, session\_flags\_t flags); + explicit **session** (session\_params const& params); + **session** (); + **session** (session\_params const& params, session\_flags\_t flags); + + +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. + +The overloads taking ``flags`` can be used to start a `session`__ in +paused mode (by passing in ``session::paused``). Note that +``add_default_plugins`` do not have an affect on constructors that +take a `session_params`__ object. It already contains the plugins to use. + + +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session_params +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session_params + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +session() +......... + +.. parsed-literal:: + + **session** (session\_params&& params, io\_context& ios, session\_flags\_t); + **session** (session\_params const& params, io\_context& ios); + **session** (session\_params&& params, io\_context& ios); + **session** (session\_params const& params, io\_context& ios, session\_flags\_t); + + +Overload of the constructor that takes an external io_context to run +the `session`__ object on. This is primarily useful for tests that may want +to run multiple sessions on a single io_context, or low resource +systems where additional threads are expensive and sharing an +io_context with other events is fine. + +.. warning:: + The `session`__ object does not cleanly terminate with an external + ``io_context``. The ``io_context::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_context, then + destruct the `session_proxy`__ object. + + +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#abort() +__ reference-Session.html#session_proxy +__ reference-Session.html#session +__ reference-Session.html#session_proxy + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +~session() +.......... + +.. parsed-literal:: + + **~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()`__. + + +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#apply_settings() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +abort() +....... + +.. parsed-literal:: + + 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:: + + struct session_proxy {}; + + +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +session_params +-------------- + +Declared in "`libtorrent/session_params.hpp`__" + + +__ include/libtorrent/session_params.hpp + +The `session_params`__ is a parameters pack for configuring the `session`__ +before it's started. + + +__ reference-Session.html#session_params +__ reference-Session.html#session + + +.. parsed-literal:: + + + struct session_params + { + **session_params** (settings\_pack&& sp); + **session_params** (); + **session_params** (settings\_pack const& sp); + **session_params** (settings\_pack&& sp + , std\:\:vector> exts); + **session_params** (settings\_pack const& sp + , std\:\:vector> exts); + + settings_pack settings; + std::vector> extensions; + dht::dht_state dht_state; + dht::dht_storage_constructor_type dht_storage_constructor; + disk_io_constructor_type disk_io_constructor; + std::map ext_state; + libtorrent::ip_filter ip_filter; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +session_params() +................ + +.. parsed-literal:: + + **session_params** (settings\_pack&& sp); + **session_params** (); + **session_params** (settings\_pack const& sp); + + +This constructor can be used to start with the default plugins +(ut_metadata, ut_pex and smart_ban). Pass a `settings_pack`__ to set the +initial settings when the `session`__ starts. + + +__ reference-Settings.html#settings_pack +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +session_params() +................ + +.. parsed-literal:: + + **session_params** (settings\_pack&& sp + , std\:\:vector> exts); + **session_params** (settings\_pack const& sp + , std\:\:vector> exts); + + +This constructor helps to configure the set of initial plugins +to be added to the `session`__ before it's started. + + +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +settings + The settings to configure the `session`__ with + + +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +extensions + the plugins to add to the `session`__ as it is constructed + + +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +dht_state + DHT node ID and node addresses to bootstrap the DHT with. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +dht_storage_constructor + function object to construct the storage object for DHT items. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +disk_io_constructor + function object to create the disk I/O subsystem. Defaults to + default_disk_io_constructor. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +ext_state + this container can be used by extensions/plugins to store settings. It's + primarily here to make it convenient to save and restore state across + sessions, using `read_session_params()`__ and `write_session_params()`__. + + +__ reference-Session.html#read_session_params() +__ reference-Session.html#write_session_params() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +ip_filter + the IP filter to use for the `session`__. This restricts which peers are allowed + to connect. As if passed to `set_ip_filter()`__. + + +__ reference-Session.html#session +__ reference-Session.html#set_ip_filter() + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +write_session_params() write_session_params_buf() read_session_params() +----------------------------------------------------------------------- + +Declared in "`libtorrent/session_params.hpp`__" + + +__ include/libtorrent/session_params.hpp + +.. parsed-literal:: + + std::vector **write_session_params_buf** (session\_params const& sp + , save\_state\_flags\_t flags = save\_state\_flags\_t\:\:all()); + session_params **read_session_params** (span buf + , save\_state\_flags\_t flags = save\_state\_flags\_t\:\:all()); + session_params **read_session_params** (bdecode\_node const& e + , save\_state\_flags\_t flags = save\_state\_flags\_t\:\:all()); + entry **write_session_params** (session\_params const& sp + , save\_state\_flags\_t flags = save\_state\_flags\_t\:\:all()); + + +These functions serialize and de-serialize a ``session_params`` object to and +from bencoded form. The `session_params`__ object is used to initialize a new +`session`__ using the state from a previous one (or by programmatically configure +the `session`__ up-front). +The flags parameter can be used to only save and load certain aspects of the +session's state. +The ``_buf`` suffix indicates the function operates on buffer rather than the +bencoded structure. +The torrents in a `session`__ are not part of the `session_params`__ state, they have +to be restored separately. + + +__ reference-Session.html#session_params +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session_params + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking + +The disk I/O can be customized in libtorrent. In previous versions, the +customization was at the level of each torrent. Now, the customization point +is at the `session`__ level. All torrents added to a `session`__ will use the same +disk I/O subsystem, as determined by the disk_io_constructor (in +`session_params`__). + +This allows the disk subsystem to also customize threading and disk job +management. + +To customize the disk subsystem, implement `disk_interface`__ and provide a +factory function to the `session`__ constructor (via `session_params`__). + +Example use: + +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session_params +__ reference-Custom_Storage.html#disk_interface +__ reference-Session.html#session +__ reference-Session.html#session_params + +.. include:: ../examples/custom_storage.cpp + :code: c++ + :tab-width: 2 + :start-after: -- example begin + :end-before: // -- example end + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +settings_interface +------------------ + +Declared in "`libtorrent/settings_pack.hpp`__" + + +__ include/libtorrent/settings_pack.hpp + +the common interface to `settings_pack`__ and the internal representation of +settings. + + +__ reference-Settings.html#settings_pack + + +.. parsed-literal:: + + + struct settings_interface + { + virtual void **set_str** (int name, std\:\:string val) = 0; + virtual void **set_int** (int name, int val) = 0; + virtual void **set_bool** (int name, bool val) = 0; + virtual bool **has_val** (int name) const = 0; + virtual std::string const& **get_str** (int name) const = 0; + virtual bool **get_bool** (int name) const = 0; + virtual int **get_int** (int name) const = 0; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +buffer_allocator_interface +-------------------------- + +Declared in "`libtorrent/disk_buffer_holder.hpp`__" + + +__ include/libtorrent/disk_buffer_holder.hpp + +the interface for freeing disk buffers, used by the `disk_buffer_holder`__. +when implementing `disk_interface`__, this must also be implemented in order +to return disk buffers back to libtorrent + + +__ reference-Custom_Storage.html#disk_buffer_holder +__ reference-Custom_Storage.html#disk_interface + + +.. parsed-literal:: + + + struct buffer_allocator_interface + { + virtual void **free_disk_buffer** (char\* b) = 0; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +disk_buffer_holder +------------------ + +Declared in "`libtorrent/disk_buffer_holder.hpp`__" + + +__ include/libtorrent/disk_buffer_holder.hpp + +The disk buffer holder acts like a ``unique_ptr`` that frees a disk buffer +when it's destructed + +If this buffer holder is moved-from, default constructed or reset, +``data()`` will return nullptr. + + + + +.. parsed-literal:: + + + struct disk_buffer_holder + { + disk_buffer_holder& **operator=** (disk\_buffer\_holder&&) & noexcept; + **disk_buffer_holder** (disk\_buffer\_holder&&) noexcept; + **disk_buffer_holder** (disk\_buffer\_holder const&) = delete; + disk_buffer_holder& **operator=** (disk\_buffer\_holder const&) = delete; + **disk_buffer_holder** (buffer\_allocator\_interface& alloc + , char\* buf, int sz) noexcept; + **disk_buffer_holder** () noexcept = default; + **~disk_buffer_holder** (); + char\* **data** () const noexcept; + void **reset** (); + void **swap** (disk\_buffer\_holder& h) noexcept; + bool **is_mutable** () const noexcept; + explicit operator **bool** () const noexcept; + std::ptrdiff_t **size** () const; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +disk_buffer_holder() +.................... + +.. parsed-literal:: + + **disk_buffer_holder** (buffer\_allocator\_interface& alloc + , char\* buf, int sz) noexcept; + + +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`__) + + +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +disk_buffer_holder() +.................... + +.. parsed-literal:: + + **disk_buffer_holder** () noexcept = default; + + +default construct a holder that does not own any buffer + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +~disk_buffer_holder() +..................... + +.. parsed-literal:: + + **~disk_buffer_holder** (); + + +frees disk buffer held by this object + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +data() +...... + +.. parsed-literal:: + + char\* **data** () const noexcept; + + +return a pointer to the held buffer, if any. Otherwise returns nullptr. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +reset() +....... + +.. parsed-literal:: + + void **reset** (); + + +free the held disk buffer, if any, and clear the holder. This sets the +holder object to a default-constructed state + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +swap() +...... + +.. parsed-literal:: + + void **swap** (disk\_buffer\_holder& h) noexcept; + + +swap pointers of two disk buffer holders. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +is_mutable() +............ + +.. parsed-literal:: + + bool **is_mutable** () const noexcept; + + +if this returns true, the buffer may not be modified in place + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +bool() +...... + +.. parsed-literal:: + + explicit operator **bool** () const noexcept; + + +implicitly convertible to true if the object is currently holding a +buffer + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +disk_observer +------------- + +Declared in "`libtorrent/disk_observer.hpp`__" + + +__ include/libtorrent/disk_observer.hpp + + + + + +.. parsed-literal:: + + + struct disk_observer + { + virtual void **on_disk** () = 0; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +on_disk() +......... + +.. parsed-literal:: + + virtual void **on_disk** () = 0; + + +called when the disk cache size has dropped +below the low watermark again and we can +resume downloading from peers + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +open_file_state +--------------- + +Declared in "`libtorrent/disk_interface.hpp`__" + + +__ include/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. + + + + +.. parsed-literal:: + + + struct open_file_state + { + file_index_t file_index; + file_open_mode_t open_mode; + time_point last_use; + }; + +.. raw:: html + + + +.. raw:: html + + [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. + + +__ reference-Bencoding.html#entry + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +open_mode + ``open_mode`` is a bitmask of the file flags this file is currently + opened with. 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. + + +__ reference-Custom_Storage.html#file_open_mode_t + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +last_use + a (high precision) timestamp of when the file was last used. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +disk_interface +-------------- + +Declared in "`libtorrent/disk_interface.hpp`__" + + +__ include/libtorrent/disk_interface.hpp + +The `disk_interface`__ is the customization point for disk I/O in libtorrent. +implement this interface and provide a factory function to the `session`__ constructor +use custom disk I/O. All functions on the disk subsystem (implementing +`disk_interface`__) are called from within libtorrent's network thread. For +disk I/O to be performed in a separate thread, the disk subsystem has to +manage that itself. + +Although the functions are called ``async_*``, they do not technically +*have* to be asynchronous, but they support being asynchronous, by +expecting the result passed back into a callback. The callbacks must be +posted back onto the network thread via the io_context object passed into +the constructor. The callbacks will be run in the network thread. + + +__ reference-Custom_Storage.html#disk_interface +__ reference-Session.html#session +__ reference-Custom_Storage.html#disk_interface + + +.. parsed-literal:: + + + struct disk_interface + { + virtual storage_holder **new_torrent** (storage\_params const& p + , std\:\:shared\_ptr const& torrent) = 0; + virtual void **remove_torrent** (storage\_index\_t) = 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_read** (storage\_index\_t storage, peer\_request const& r + , std\:\:function handler + , disk\_job\_flags\_t flags = {}) = 0; + virtual void **async_hash** (storage\_index\_t storage, piece\_index\_t piece, span v2 + , disk\_job\_flags\_t flags + , std\:\:function handler) = 0; + virtual void **async_hash2** (storage\_index\_t storage, piece\_index\_t piece, int offset, 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_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 **update_stats_counters** (counters& c) const = 0; + virtual std::vector **get_status** (storage\_index\_t) const = 0; + virtual void **abort** (bool wait) = 0; + virtual void **submit_jobs** () = 0; + virtual void **settings_updated** () = 0; + + static constexpr disk_job_flags_t **force_copy** = 0_bit; + static constexpr disk_job_flags_t **sequential_access** = 3_bit; + static constexpr disk_job_flags_t **volatile_read** = 4_bit; + static constexpr disk_job_flags_t **v1_hash** = 5_bit; + static constexpr disk_job_flags_t **flush_piece** = 7_bit; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +new_torrent() +............. + +.. parsed-literal:: + + virtual storage_holder **new_torrent** (storage\_params const& p + , std\:\:shared\_ptr const& torrent) = 0; + + +this is called when a new torrent is added. The shared_ptr can be +used to hold the internal torrent object alive as long as there are +outstanding disk operations on the storage. +The returned `storage_holder`__ is an owning reference to the underlying +storage that was just created. It is fundamentally a storage_index_t + + +__ reference-Custom_Storage.html#storage_holder + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +remove_torrent() +................ + +.. parsed-literal:: + + virtual void **remove_torrent** (storage\_index\_t) = 0; + + +remove the storage with the specified index. This is not expected to +delete any files from disk, just to clean up any resources associated +with the specified storage. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +async_read() async_write() +.......................... + +.. parsed-literal:: + + 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_read** (storage\_index\_t storage, peer\_request const& r + , std\:\:function handler + , disk\_job\_flags\_t flags = {}) = 0; + + +perform a read or write operation from/to the specified storage +index and the specified request. When the operation completes, call +handler possibly with a `disk_buffer_holder`__, holding the buffer with +the result. Flags may be set to affect the read operation. See +disk_job_flags_t. + +The `disk_observer`__ is a callback to indicate that +the store buffer/disk write queue is below the watermark to let peers +start writing buffers to disk again. When ``async_write()`` returns +``true``, indicating the write queue is full, the peer will stop +further writes and wait for the passed-in ``disk_observer`` to be +notified before resuming. + +Note that for ``async_read``, the `peer_request`__ (``r``) is not +necessarily aligned to blocks (but it is most of the time). However, +all writes (passed to ``async_write``) are guaranteed to be block +aligned. + + +__ reference-Custom_Storage.html#disk_buffer_holder +__ reference-Custom_Storage.html#disk_observer +__ reference-Core.html#peer_request + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +async_hash() +............ + +.. parsed-literal:: + + virtual void **async_hash** (storage\_index\_t storage, piece\_index\_t piece, span v2 + , disk\_job\_flags\_t flags + , std\:\:function handler) = 0; + + +Compute hash(es) for the specified piece. Unless the v1_hash flag is +set (in ``flags``), the SHA-1 hash of the whole piece does not need +to be computed. + +The `v2` span is optional and can be empty, which means v2 hashes +should not be computed. If v2 is non-empty it must be at least large +enough to hold all v2 blocks in the piece, and this function will +fill in the span with the SHA-256 block hashes of the piece. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +async_hash2() +............. + +.. parsed-literal:: + + virtual void **async_hash2** (storage\_index\_t storage, piece\_index\_t piece, int offset, disk\_job\_flags\_t flags + , std\:\:function handler) = 0; + + +computes the v2 hash (SHA-256) of a single block. The block at +``offset`` in piece ``piece``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +async_move_storage() +.................... + +.. parsed-literal:: + + virtual void **async_move_storage** (storage\_index\_t storage, std\:\:string p, move\_flags\_t flags + , std\:\:function handler) = 0; + + +called to request the files for the specified storage/torrent be +moved to a new location. It is the disk I/O object's responsibility +to synchronize this with any currently outstanding disk operations to +the storage. Whether files are replaced at the destination path or +not is controlled by ``flags`` (see `move_flags_t`__). + + +__ reference-Storage.html#move_flags_t + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +async_release_files() +..................... + +.. parsed-literal:: + + virtual void **async_release_files** (storage\_index\_t storage + , std\:\:function handler = std\:\:function()) = 0; + + +This is called on disk I/O objects to request they close all open +files for the specified storage/torrent. If file handles are not +pooled/cached, it can be a no-op. For truly asynchronous disk I/O, +this should provide at least one point in time when all files are +closed. It is possible that later asynchronous operations will +re-open some of the files, by the time this completion handler is +called, that's fine. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +async_check_files() +................... + +.. parsed-literal:: + + virtual void **async_check_files** (storage\_index\_t storage + , add\_torrent\_params const\* resume\_data + , aux\:\:vector links + , std\:\:function handler) = 0; + + +this is called when torrents are added to validate their resume data +against the files on disk. This function is expected to do a few things: + +if ``links`` is non-empty, it contains a string for each file in the +torrent. The string being a path to an existing identical file. The +default behavior is to create hard links of those files into the +storage of the new torrent (specified by ``storage``). An empty +string indicates that there is no known identical file. This is part +of the "mutable torrent" feature, where files can be reused from +other torrents. + +The ``resume_data`` points the resume data passed in by the client. + +If the ``resume_data->flags`` field has the seed_mode flag set, all +files/pieces are expected to be on disk already. This should be +verified. Not just the existence of the file, but also that it has +the correct size. + +Any file with a piece set in the ``resume_data->have_pieces`` bitmask +should exist on disk, this should be verified. Pad files and files +with zero priority may be skipped. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +async_stop_torrent() +.................... + +.. parsed-literal:: + + virtual void **async_stop_torrent** (storage\_index\_t storage + , std\:\:function handler = std\:\:function()) = 0; + + +This is called when a torrent is stopped. It gives the disk I/O +object an opportunity to flush any data to disk that's currently kept +cached. This function should at least do the same thing as +`async_release_files()`__. + + +__ reference-Custom_Storage.html#async_release_files() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +async_rename_file() +................... + +.. parsed-literal:: + + virtual void **async_rename_file** (storage\_index\_t storage + , file\_index\_t index, std\:\:string name + , std\:\:function handler) = 0; + + +This function is called when the name of a file in the specified +storage has been requested to be renamed. The disk I/O object is +responsible for renaming the file without racing with other +potentially outstanding operations against the file (such as read, +write, move, etc.). + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +async_delete_files() +.................... + +.. parsed-literal:: + + virtual void **async_delete_files** (storage\_index\_t storage, remove\_flags\_t options + , std\:\:function handler) = 0; + + +This function is called when some file(s) on disk have been requested +to be removed by the client. ``storage`` indicates which torrent is +referred to. See `session_handle`__ for ``remove_flags_t`` flags +indicating which files are to be removed. +e.g. `session_handle::delete_files`__ - delete all files +`session_handle::delete_partfile`__ - only delete part file. + + +__ reference-Session.html#session_handle +__ reference-Session.html#delete_files +__ reference-Session.html#delete_partfile + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +async_set_file_priority() +......................... + +.. parsed-literal:: + + virtual void **async_set_file_priority** (storage\_index\_t storage + , aux\:\:vector prio + , std\:\:function)> handler) = 0; + + +This is called to set the priority of some or all files. Changing the +priority from or to 0 may involve moving data to and from the +partfile. The disk I/O object is responsible for correctly +synchronizing this work to not race with any potentially outstanding +asynchronous operations affecting these files. + +``prio`` is a vector of the file priority for all files. If it's +shorter than the total number of files in the torrent, they are +assumed to be set to the default priority. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +async_clear_piece() +................... + +.. parsed-literal:: + + virtual void **async_clear_piece** (storage\_index\_t storage, piece\_index\_t index + , std\:\:function handler) = 0; + + +This is called when a piece fails the hash check, to ensure there are +no outstanding disk operations to the piece before blocks are +re-requested from peers to overwrite the existing blocks. The disk I/O +object does not need to perform any action other than synchronize +with all outstanding disk operations to the specified piece before +posting the result back. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +update_stats_counters() +....................... + +.. parsed-literal:: + + virtual void **update_stats_counters** (counters& c) const = 0; + + +`update_stats_counters()`__ is called to give the disk storage an +opportunity to update gauges in the ``c`` stats `counters`__, that aren't +updated continuously as operations are performed. This is called +before a snapshot of the `counters`__ are passed to the client. + + +__ reference-Custom_Storage.html#update_stats_counters() +__ reference-Stats.html#counters +__ reference-Stats.html#counters + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +get_status() +............ + +.. parsed-literal:: + + virtual std::vector **get_status** (storage\_index\_t) const = 0; + + +Return a list of all the files that are currently open for the +specified storage/torrent. This is is just used for the client to +query the currently open files, and which modes those files are open +in. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +abort() +....... + +.. parsed-literal:: + + virtual void **abort** (bool wait) = 0; + + +this is called when the `session`__ is starting to shut down. The disk +I/O object is expected to flush any outstanding write jobs, cancel +hash jobs and initiate tearing down of any internal threads. If +``wait`` is true, this should be asynchronous. i.e. this call should +not return until all threads have stopped and all jobs have either +been aborted or completed and the disk I/O object is ready to be +destructed. + + +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +submit_jobs() +............. + +.. parsed-literal:: + + virtual void **submit_jobs** () = 0; + + +This will be called after a batch of disk jobs has been issues (via +the ``async_*`` ). It gives the disk I/O object an opportunity to +notify any potential condition variables to wake up the disk +thread(s). The ``async_*`` calls can of course also notify condition +variables, but doing it in this call allows for batching jobs, by +issuing the notification once for a collection of jobs. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +settings_updated() +.................. + +.. parsed-literal:: + + virtual void **settings_updated** () = 0; + + +This is called to notify the disk I/O object that the settings have +been updated. In the disk io constructor, a `settings_interface`__ +reference is passed in. Whenever these settings are updated, this +function is called to allow the disk I/O object to react to any +changed settings relevant to its operations. + + +__ reference-Custom_Storage.html#settings_interface + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +force_copy + force making a copy of the cached block, rather than getting a + reference to a block already in the cache. This is used the block is + expected to be overwritten very soon, by async_write()`, and we need + access to the previous content. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +sequential_access + hint that there may be more disk operations with sequential access to + the file + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +volatile_read + don't keep the read block in cache. This is a hint that this block is + unlikely to be read again anytime soon, and caching it would be + wasteful. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +v1_hash + compute a v1 piece hash. This is only used by the `async_hash()`__ call. + If this flag is not set in the `async_hash()`__ call, the SHA-1 piece + hash does not need to be computed. + + +__ reference-Custom_Storage.html#async_hash() +__ reference-Custom_Storage.html#async_hash() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +flush_piece + this flag instructs a hash job that we just completed this piece, and + it should be flushed to disk + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +storage_holder +-------------- + +Declared in "`libtorrent/disk_interface.hpp`__" + + +__ include/libtorrent/disk_interface.hpp + +a unique, owning, reference to the storage of a torrent in a disk io +subsystem (class that implements `disk_interface`__). This is held by the +internal libtorrent torrent object to tie the storage object allocated +for a torrent to the lifetime of the internal torrent object. When a +torrent is removed from the `session`__, this holder is destructed and will +inform the disk object. + + +__ reference-Custom_Storage.html#disk_interface +__ reference-Session.html#session + + +.. parsed-literal:: + + + struct storage_holder + { + **~storage_holder** (); + **storage_holder** (storage\_index\_t idx, disk\_interface& disk\_io); + **storage_holder** () = default; + explicit operator **bool** () const; + operator **storage_index_t** () const; + void **reset** (); + storage_holder& **operator=** (storage\_holder const&) = delete; + **storage_holder** (storage\_holder const&) = delete; + **storage_holder** (storage\_holder&& rhs) noexcept; + storage_holder& **operator=** (storage\_holder&& rhs) noexcept; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_open_mode_t +---------------- + +Declared in "`libtorrent/disk_interface.hpp`__" + + +__ include/libtorrent/disk_interface.hpp + +.. raw:: html + + + +read_only + open the file for reading only + + + +.. raw:: html + + + +write_only + open the file for writing only + + + +.. raw:: html + + + +read_write + open the file for reading and writing + + + +.. raw:: html + + + +rw_mask + the mask for the bits determining read or write mode + + + +.. raw:: html + + + +sparse + open the file in sparse mode (if supported by the + filesystem). + + + +.. raw:: html + + + +no_atime + don't update the access timestamps on the file (if + supported by the operating system and filesystem). + this generally improves disk performance. + + + +.. raw:: html + + + +random_access + open the file for random access. This disables read-ahead + logic + + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking + +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: + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +settings_pack +------------- + +Declared in "`libtorrent/settings_pack.hpp`__" + + +__ reference-Session.html#session +__ reference-Settings.html#settings_pack +__ reference-Settings.html#settings_pack +__ reference-Session.html#session +__ reference-Settings.html#settings_pack +__ include/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. + + +.. include:: settings-ref.rst + + + + + +.. parsed-literal:: + + + struct settings_pack final : settings_interface + { + friend void **apply_pack_impl** (settings\_pack const\* + , aux\:\:session\_settings\_single\_thread& + , std\:\:vector\*); + void **set_int** (int name, flags\:\:bitfield\_flag const val); + void **set_str** (int name, std\:\:string val) override; + void **set_bool** (int name, bool val) override; + void **set_int** (int name, int val) override; + bool **has_val** (int name) const override; + void **clear** (); + void **clear** (int name); + std::string const& **get_str** (int name) const override; + bool **get_bool** (int name) const override; + int **get_int** (int name) const override; + void **for_each** (Fun&& f) 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, + write_through, + }; + + 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, + }; + }; + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +set_int() set_str() set_bool() +.............................. + +.. parsed-literal:: + + void **set_int** (int name, flags\:\:bitfield\_flag const val); + void **set_str** (int name, std\:\:string val) override; + void **set_bool** (int name, bool val) override; + void **set_int** (int name, int val) override; + + +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. + + +__ reference-Settings.html#settings_pack + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +has_val() +......... + +.. parsed-literal:: + + bool **has_val** (int name) const override; + + +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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +clear() +....... + +.. parsed-literal:: + + void **clear** (); + + +clear the settings pack from all settings + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +clear() +....... + +.. parsed-literal:: + + void **clear** (int name); + + +clear a specific setting from the pack + + + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +get_bool() get_str() get_int() +.............................. + +.. parsed-literal:: + + std::string const& **get_str** (int name) const override; + bool **get_bool** (int name) const override; + int **get_int** (int name) const override; + + +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. + + +__ reference-Settings.html#settings_pack + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum type_bases +............... + +Declared in "`libtorrent/settings_pack.hpp`__" + + +__ include/libtorrent/settings_pack.hpp + ++------------------+-------+-------------+ +| name | value | description | ++==================+=======+=============+ +| string_type_base | 0 | | ++------------------+-------+-------------+ +| int_type_base | 16384 | | ++------------------+-------+-------------+ +| bool_type_base | 32768 | | ++------------------+-------+-------------+ +| type_mask | 49152 | | ++------------------+-------+-------------+ +| index_mask | 16383 | | ++------------------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum suggest_mode_t +................... + +Declared in "`libtorrent/settings_pack.hpp`__" + + +__ include/libtorrent/settings_pack.hpp + ++----------------------+-------+-------------+ +| name | value | description | ++======================+=======+=============+ +| no_piece_suggestions | 0 | | ++----------------------+-------+-------------+ +| suggest_read_cache | 1 | | ++----------------------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum choking_algorithm_t +........................ + +Declared in "`libtorrent/settings_pack.hpp`__" + + +__ include/libtorrent/settings_pack.hpp + ++-----------------------------+-------+-------------------------------------------------------------------+ +| name | value | description | ++=============================+=======+===================================================================+ +| fixed_slots_choker | 0 | This is the traditional choker with a fixed number of unchoke | +| | | slots (as specified by `settings_pack::unchoke_slots_limit`__). | +| | | | ++-----------------------------+-------+-------------------------------------------------------------------+ +| rate_based_choker | 2 | 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_choker | 3 | | ++-----------------------------+-------+-------------------------------------------------------------------+ + + +__ reference-Settings.html#unchoke_slots_limit +__ reference-Settings.html#rate_choker_initial_threshold + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum seed_choking_algorithm_t +............................. + +Declared in "`libtorrent/settings_pack.hpp`__" + + +__ include/libtorrent/settings_pack.hpp + ++----------------+-------+--------------------------------------------------------------------+ +| name | value | description | ++================+=======+====================================================================+ +| round_robin | 0 | 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. | +| | | | ++----------------+-------+--------------------------------------------------------------------+ +| fastest_upload | 1 | unchokes the peers we can send to the fastest. This might be a | +| | | bit more reliable in utilizing all available capacity. | +| | | | ++----------------+-------+--------------------------------------------------------------------+ +| anti_leech | 2 | 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. | +| | | | ++----------------+-------+--------------------------------------------------------------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum io_buffer_mode_t +..................... + +Declared in "`libtorrent/settings_pack.hpp`__" + + +__ include/libtorrent/settings_pack.hpp + ++-----------------------------------------------+-------+-------------+ +| name | value | description | ++===============================================+=======+=============+ +| enable_os_cache | 0 | | ++-----------------------------------------------+-------+-------------+ +| deprecated_disable_os_cache_for_aligned_files | 1 | | ++-----------------------------------------------+-------+-------------+ +| disable_os_cache | 2 | | ++-----------------------------------------------+-------+-------------+ +| write_through | 3 | | ++-----------------------------------------------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum bandwidth_mixed_algo_t +........................... + +Declared in "`libtorrent/settings_pack.hpp`__" + + +__ include/libtorrent/settings_pack.hpp + ++-------------------+-------+-------------------------------------------------------------+ +| name | value | description | ++===================+=======+=============================================================+ +| prefer_tcp | 0 | disables the mixed mode bandwidth balancing | +| | | | ++-------------------+-------+-------------------------------------------------------------+ +| peer_proportional | 1 | does not throttle uTP, throttles TCP to the same proportion | +| | | of throughput as there are TCP connections | +| | | | ++-------------------+-------+-------------------------------------------------------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum enc_policy +............... + +Declared in "`libtorrent/settings_pack.hpp`__" + + +__ include/libtorrent/settings_pack.hpp + ++-------------+-------+---------------------------------------------------------------------+ +| name | value | description | ++=============+=======+=====================================================================+ +| pe_forced | 0 | 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_enabled | 1 | 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_disabled | 2 | only non-encrypted connections are allowed. | +| | | | ++-------------+-------+---------------------------------------------------------------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum enc_level +.............. + +Declared in "`libtorrent/settings_pack.hpp`__" + + +__ include/libtorrent/settings_pack.hpp + ++--------------+-------+--------------------------------+ +| name | value | description | ++==============+=======+================================+ +| pe_plaintext | 1 | use only plain text encryption | +| | | | ++--------------+-------+--------------------------------+ +| pe_rc4 | 2 | use only RC4 encryption | +| | | | ++--------------+-------+--------------------------------+ +| pe_both | 3 | allow both | +| | | | ++--------------+-------+--------------------------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum proxy_type_t +................. + +Declared in "`libtorrent/settings_pack.hpp`__" + + +__ include/libtorrent/settings_pack.hpp + ++-----------+-------+-------------------------------------------------------------------------------+ +| name | value | description | ++===========+=======+===============================================================================+ +| none | 0 | No proxy server is used and all other fields are ignored. | +| | | | ++-----------+-------+-------------------------------------------------------------------------------+ +| socks4 | 1 | The server is assumed to be a `SOCKS4 server`_ that requires a | +| | | username. | +| | | | +| | | .. _`SOCKS4 server`: http://www.ufasoft.com/doc/socks4_protocol.htm | +| | | | ++-----------+-------+-------------------------------------------------------------------------------+ +| socks5 | 2 | 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_pw | 3 | 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 | +| | | | ++-----------+-------+-------------------------------------------------------------------------------+ +| http | 4 | 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_pw | 5 | The server is assumed to be an HTTP proxy that requires user | +| | | authorization. The username and password will be sent to the proxy. | +| | | | ++-----------+-------+-------------------------------------------------------------------------------+ +| i2p_proxy | 6 | route through a i2p SAM proxy | +| | | | ++-----------+-------+-------------------------------------------------------------------------------+ + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +name_for_setting() setting_by_name() +------------------------------------ + +Declared in "`libtorrent/settings_pack.hpp`__" + + +__ include/libtorrent/settings_pack.hpp + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +default_settings() +------------------ + +Declared in "`libtorrent/settings_pack.hpp`__" + + +__ include/libtorrent/settings_pack.hpp + +.. parsed-literal:: + + settings_pack **default_settings** (); + + +returns a `settings_pack`__ with every setting set to its default value + + +__ reference-Settings.html#settings_pack + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +high_performance_seed() min_memory_usage() +------------------------------------------ + +Declared in "`libtorrent/session.hpp`__" + + +__ include/libtorrent/session.hpp + +.. parsed-literal:: + + 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. + + +__ reference-Session.html#session +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +generate_fingerprint() +---------------------- + +Declared in "`libtorrent/fingerprint.hpp`__" + + +__ include/libtorrent/fingerprint.hpp + +.. parsed-literal:: + + 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 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. + + +__ reference-Settings.html#settings_pack + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +announce_infohash +----------------- + +Declared in "`libtorrent/announce_entry.hpp`__" + + +__ include/libtorrent/announce_entry.hpp + + + + + +.. parsed-literal:: + + + struct announce_infohash + { + std::string message; + error_code last_error; + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +message + if this tracker has returned an error or warning message + that message is stored here + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +last_error + if this tracker failed the last time it was contacted + this error code specifies what error occurred + + + +.. raw:: html + + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +fails + the number of times in a row we have failed to announce to this + tracker. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +updating + true while we're waiting for a response from the tracker. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +complete_sent + set to true when we send a event=completed. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +announce_endpoint +----------------- + +Declared in "`libtorrent/announce_entry.hpp`__" + + +__ include/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 + + + + +.. parsed-literal:: + + + struct announce_endpoint + { + **announce_endpoint** (); + + tcp::endpoint local_endpoint; + aux::array info_hashes; + bool **enabled** = true; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +local_endpoint + the local endpoint of the listen interface associated with this endpoint + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +info_hashes + info_hashes[0] is the v1 info hash (SHA1) + info_hashes[1] is the v2 info hash (truncated SHA-256) + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +enabled + set to false to not announce from this endpoint + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +announce_entry +-------------- + +Declared in "`libtorrent/announce_entry.hpp`__" + + +__ include/libtorrent/announce_entry.hpp + +this class holds information about one bittorrent tracker, as it +relates to a specific torrent. + + + + +.. parsed-literal:: + + + struct announce_entry + { + **announce_entry** (); + **~announce_entry** (); + explicit **announce_entry** (string\_view u); + **announce_entry** (announce\_entry const&); + announce_entry& **operator=** (announce\_entry const&) &; + + enum tracker_source + { + source_torrent, + source_client, + source_magnet_link, + source_tex, + }; + + std::string url; + std::string trackerid; + std::vector endpoints; + std::uint8_t **tier** = 0; + std::uint8_t **fail_limit** = 0; + std::uint8_t source:4; + bool verified:1; + }; + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +announce_entry() ~announce_entry() operator=() +.............................................. + +.. parsed-literal:: + + **announce_entry** (); + **~announce_entry** (); + explicit **announce_entry** (string\_view u); + **announce_entry** (announce\_entry const&); + announce_entry& **operator=** (announce\_entry const&) &; + + +constructs a tracker announce `entry`__ with ``u`` as the URL. + + +__ reference-Bencoding.html#entry + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum tracker_source +................... + +Declared in "`libtorrent/announce_entry.hpp`__" + + +__ include/libtorrent/announce_entry.hpp + ++--------------------+-------+---------------------------------------------------------------------------+ +| name | value | description | ++====================+=======+===========================================================================+ +| source_torrent | 1 | the tracker was part of the .torrent file | +| | | | ++--------------------+-------+---------------------------------------------------------------------------+ +| source_client | 2 | the tracker was added programmatically via the `add_tracker()`__ function | +| | | | ++--------------------+-------+---------------------------------------------------------------------------+ +| source_magnet_link | 4 | the tracker was part of a magnet link | +| | | | ++--------------------+-------+---------------------------------------------------------------------------+ +| source_tex | 8 | the tracker was received from the swarm via tracker exchange | +| | | | ++--------------------+-------+---------------------------------------------------------------------------+ + + +__ reference-Torrent_Info.html#add_tracker() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +url + tracker URL as it appeared in the torrent file + + + +.. raw:: html + + + +.. raw:: html + + [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). + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +endpoints + each local listen socket (endpoint) will announce to the tracker. This + list contains state per endpoint. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +tier + the tier this tracker belongs to + + + +.. raw:: html + + + +.. raw:: html + + [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 + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +source + a bitmask specifying which sources we got this tracker from. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +verified + set to true the first time we receive a valid response + from this tracker. + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +hasher +------ + +Declared in "`libtorrent/hasher.hpp`__" + + +__ include/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``. + + +__ reference-Utility.html#hasher +__ reference-Utility.html#hasher +__ reference-Utility.html#hasher + + +.. parsed-literal:: + + + class hasher + { + **hasher** (); + explicit **hasher** (span data); + **hasher** (hasher const&); + **hasher** (char const\* data, int len); + hasher& **operator=** (hasher const&) &; + hasher& **update** (span data); + hasher& **update** (char const\* data, int len); + sha1_hash **final** (); + void **reset** (); + }; + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +hasher() operator=() +.................... + +.. parsed-literal:: + + explicit **hasher** (span data); + **hasher** (hasher const&); + **hasher** (char const\* data, int len); + hasher& **operator=** (hasher const&) &; + + +this is the same as default constructing followed by a call to +``update(data, len)``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +update() +........ + +.. parsed-literal:: + + hasher& **update** (span data); + hasher& **update** (char const\* data, int len); + + +append the following bytes to what is being hashed + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +final() +....... + +.. parsed-literal:: + + sha1_hash **final** (); + + +returns the SHA-1 digest of the buffers previously passed to +`update()`__ and the `hasher`__ constructor. + + +__ reference-Utility.html#update() +__ reference-Utility.html#hasher + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +reset() +....... + +.. parsed-literal:: + + void **reset** (); + + +restore the `hasher`__ state to be as if the `hasher`__ has just been +default constructed. + + +__ reference-Utility.html#hasher +__ reference-Utility.html#hasher + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +hasher256 +--------- + +Declared in "`libtorrent/hasher.hpp`__" + + +__ include/libtorrent/hasher.hpp + + + + + +.. parsed-literal:: + + + class hasher256 + { + **hasher256** (); + **hasher256** (hasher256 const&); + explicit **hasher256** (span data); + **hasher256** (char const\* data, int len); + hasher256& **operator=** (hasher256 const&) &; + hasher256& **update** (span data); + hasher256& **update** (char const\* data, int len); + sha256_hash **final** (); + void **reset** (); + **~hasher256** (); + }; + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +operator=() hasher256() +....................... + +.. parsed-literal:: + + **hasher256** (hasher256 const&); + explicit **hasher256** (span data); + **hasher256** (char const\* data, int len); + hasher256& **operator=** (hasher256 const&) &; + + +this is the same as default constructing followed by a call to +``update(data, len)``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +update() +........ + +.. parsed-literal:: + + hasher256& **update** (span data); + hasher256& **update** (char const\* data, int len); + + +append the following bytes to what is being hashed + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +final() +....... + +.. parsed-literal:: + + sha256_hash **final** (); + + +returns the SHA-1 digest of the buffers previously passed to +`update()`__ and the `hasher`__ constructor. + + +__ reference-Utility.html#update() +__ reference-Utility.html#hasher + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +reset() +....... + +.. parsed-literal:: + + void **reset** (); + + +restore the `hasher`__ state to be as if the `hasher`__ has just been +default constructed. + + +__ reference-Utility.html#hasher +__ reference-Utility.html#hasher + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +bitfield +-------- + +Declared in "`libtorrent/bitfield.hpp`__" + + +__ include/libtorrent/bitfield.hpp + +The `bitfield`__ type stores any number of bits as a `bitfield`__ +in a heap allocated array. + + +__ reference-Utility.html#bitfield +__ reference-Utility.html#bitfield + + +.. parsed-literal:: + + + struct bitfield + { + **bitfield** (bitfield const& rhs); + **bitfield** (bitfield&& rhs) noexcept = default; + **bitfield** (char const\* b, int bits); + **bitfield** () noexcept = default; + **bitfield** (int bits, bool val); + explicit **bitfield** (int bits); + void **assign** (char const\* b, int const bits); + bool **operator[]** (int index) const noexcept; + bool **get_bit** (int index) const noexcept; + void **clear_bit** (int index) noexcept; + void **set_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 const* **data** () const noexcept; + char\* **data** () noexcept; + void **swap** (bitfield& rhs) noexcept; + int **count** () const noexcept; + int **find_first_set** () const noexcept; + int **find_last_clear** () const noexcept; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +bitfield() +.......... + +.. parsed-literal:: + + **bitfield** (bitfield const& rhs); + **bitfield** (bitfield&& rhs) noexcept = default; + **bitfield** (char const\* b, int bits); + **bitfield** () noexcept = default; + **bitfield** (int bits, bool val); + explicit **bitfield** (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). + + +__ reference-Utility.html#bitfield +__ reference-Utility.html#bitfield +__ reference-Utility.html#bitfield +__ reference-Utility.html#bitfield + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +assign() +........ + +.. parsed-literal:: + + 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. + + +__ reference-Utility.html#bitfield + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +operator[]() get_bit() +...................... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +clear_bit() set_bit() +..................... + +.. parsed-literal:: + + void **clear_bit** (int index) noexcept; + void **set_bit** (int index) noexcept; + + +set bit at ``index`` to 0 (clear_bit) or 1 (set_bit). + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +all_set() +......... + +.. parsed-literal:: + + bool **all_set** () const noexcept; + + +returns true if all bits in the `bitfield`__ are set + + +__ reference-Utility.html#bitfield + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +none_set() +.......... + +.. parsed-literal:: + + bool **none_set** () const noexcept; + + +returns true if no bit in the `bitfield`__ is set + + +__ reference-Utility.html#bitfield + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +size() +...... + +.. parsed-literal:: + + int **size** () const noexcept; + + +returns the size of the `bitfield`__ in bits. + + +__ reference-Utility.html#bitfield + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +num_words() +........... + +.. parsed-literal:: + + int **num_words** () const noexcept; + + +returns the number of 32 bit words are needed to represent all bits in +this `bitfield`__. + + +__ reference-Utility.html#bitfield + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +empty() +....... + +.. parsed-literal:: + + bool **empty** () const noexcept; + + +returns true if the `bitfield`__ has zero size. + + +__ reference-Utility.html#bitfield + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +data() +...... + +.. parsed-literal:: + + char const* **data** () const noexcept; + char\* **data** () noexcept; + + +returns a pointer to the internal buffer of the `bitfield`__, or +``nullptr`` if it's empty. + + +__ reference-Utility.html#bitfield + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +swap() +...... + +.. parsed-literal:: + + void **swap** (bitfield& rhs) noexcept; + + +swaps the bit-fields two variables refer to + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +count() +....... + +.. parsed-literal:: + + int **count** () const noexcept; + + +count the number of bits in the `bitfield`__ that are set to 1. + + +__ reference-Utility.html#bitfield + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +find_first_set() +................ + +.. parsed-literal:: + + int **find_first_set** () const noexcept; + + +returns the index of the first set bit in the `bitfield`__, i.e. 1 bit. + + +__ reference-Utility.html#bitfield + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +find_last_clear() +................. + +.. parsed-literal:: + + int **find_last_clear** () const noexcept; + + +returns the index to the last cleared bit in the `bitfield`__, i.e. 0 bit. + + +__ reference-Utility.html#bitfield + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_status +-------------- + +Declared in "`libtorrent/torrent_status.hpp`__" + + +__ include/libtorrent/torrent_status.hpp + +holds a snapshot of the status of a torrent, as queried by +`torrent_handle::status()`__. + + +__ reference-Torrent_Handle.html#status() + + +.. parsed-literal:: + + + struct torrent_status + { + bool **operator==** (torrent\_status const& st) const; + + enum state_t + { + checking_files, + downloading_metadata, + downloading, + finished, + seeding, + unused_enum_for_backwards_compatibility_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 torrent_file; + time_duration **next_announce** = seconds{0}; + 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 pieces; + typed_bitfield 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; + info_hash_t info_hashes; + time_point last_upload; + time_point last_download; + seconds active_duration; + seconds finished_duration; + seconds seeding_duration; + torrent_flags_t **flags** {}; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +operator==() +............ + +.. parsed-literal:: + + 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. + + +__ reference-Torrent_Handle.html#torrent_handle + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum state_t +............ + +Declared in "`libtorrent/torrent_status.hpp`__" + + +__ include/libtorrent/torrent_status.hpp + ++----------------------------------------------------+-------+------------------------------------------------------------+ +| name | value | description | ++====================================================+=======+============================================================+ +| checking_files | 1 | The torrent has not started its download yet, and is | +| | | currently checking existing files. | +| | | | ++----------------------------------------------------+-------+------------------------------------------------------------+ +| downloading_metadata | 2 | The torrent is trying to download metadata from peers. | +| | | This implies the ut_metadata extension is in use. | +| | | | ++----------------------------------------------------+-------+------------------------------------------------------------+ +| downloading | 3 | 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. | +| | | | ++----------------------------------------------------+-------+------------------------------------------------------------+ +| finished | 4 | 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. | +| | | | ++----------------------------------------------------+-------+------------------------------------------------------------+ +| seeding | 5 | In this state the torrent has finished downloading and | +| | | is a pure seeder. | +| | | | ++----------------------------------------------------+-------+------------------------------------------------------------+ +| unused_enum_for_backwards_compatibility_allocating | 6 | If the torrent was started in full allocation mode, this | +| | | indicates that the (disk) storage for the torrent is | +| | | allocated. | +| | | | ++----------------------------------------------------+-------+------------------------------------------------------------+ +| checking_resume_data | 7 | 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. | +| | | | ++----------------------------------------------------+-------+------------------------------------------------------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +handle + a handle to the torrent whose status the object represents. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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; + + + +.. raw:: html + + + +.. raw:: html + + [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 + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error_file_ssl_ctx + the error occurred setting up the SSL context + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error_file_metadata + the error occurred while loading the metadata for the torrent + + + +.. raw:: html + + + +.. raw:: html + + [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. + + +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error_file_partfile + the error occurred with the partfile + + + +.. raw:: html + + + +.. raw:: html + + [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``. + + +__ reference-Session.html#async_add_torrent() +__ reference-Session.html#add_torrent() + +.. raw:: html + + + +.. raw:: html + + [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``. + + +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [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``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +next_announce + the time until the torrent will announce itself to the tracker. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + + +.. raw:: html + + [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``. + + +__ reference-Session.html#session +__ reference-Stats.html#counters + +.. raw:: html + + + + +.. raw:: html + + [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. + + +__ reference-Session.html#session +__ reference-Stats.html#counters +__ reference-Session.html#session +__ reference-Stats.html#counters + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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``). + + +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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). + Once a torrent becomes seed, any piece- and file priorities are + forgotten and all bytes are considered "wanted". + + + +.. raw:: html + + + +.. raw:: html + + [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. + Once a torrent becomes seed, any piece- and file priorities are + forgotten and all bytes are considered "wanted". + + + +.. raw:: html + + + + +.. raw:: html + + [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. + + +__ reference-Stats.html#counters + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +added_time + the posix-time when this torrent was added. i.e. what ``time(nullptr)`` + returned at the time. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +completed_time + the posix-time when this torrent was finished. If the torrent is not + yet finished, this is 0. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +last_seen_complete + the time when we, or one of our peers, last saw a complete copy of + this torrent. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +storage_mode + The allocation mode for the torrent. See `storage_mode_t`__ for the + options. For more information, see `storage allocation`__. + + +__ reference-Storage.html#storage_mode_t +__ manual-ref.html#storage-allocation + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +queue_position + the position this torrent has in the download + queue. If the torrent is a seed or finished, this is -1. + + + +.. raw:: html + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +num_seeds + the number of peers that are seeding that this client is + currently connected to. + + + +.. raw:: html + + + +.. raw:: html + + [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()`__. + + +__ reference-Torrent_Handle.html#get_peer_info() + +.. raw:: html + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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). + + + +.. raw:: html + + + +.. raw:: html + + [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. + + +__ reference-Torrent_Handle.html#get_download_queue() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +num_uploads + the number of unchoked peers in this torrent. + + + +.. raw:: html + + + +.. raw:: html + + [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``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +uploads_limit + the set limit of upload slots (unchoked peers) for this torrent. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +connections_limit + the set limit of number of connections for this torrent. + + + +.. raw:: html + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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 + + +__ manual-ref.html#queuing + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +state + the main state the torrent is in. See `torrent_status::state_t`__. + + +__ reference-Torrent_Status.html#state_t + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +is_seeding + true if all pieces have been downloaded. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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). + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +has_incoming + true if there has ever been an incoming connection attempt to this + torrent. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +info_hashes + the info-hash for this torrent + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + +last_upload last_download + the timestamps of the last time this torrent uploaded or downloaded + payload to any peer. + + + +.. raw:: html + + + + + +.. raw:: html + + [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. + + +__ reference-Stats.html#counters +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +flags + reflects several of the torrent's flags. For more + information, see ``torrent_handle::flags()``. + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +peer_request +------------ + +Declared in "`libtorrent/peer_request.hpp`__" + + +__ include/libtorrent/peer_request.hpp + +represents a byte range within a piece. Internally this is is used for +incoming piece requests. + + + + +.. parsed-literal:: + + + struct peer_request + { + bool **operator==** (peer\_request const& r) const; + + piece_index_t piece; + int start; + int length; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +operator==() +............ + +.. parsed-literal:: + + bool **operator==** (peer\_request const& r) const; + + +returns true if the right hand side `peer_request`__ refers to the same +range as this does. + + +__ reference-Core.html#peer_request + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +piece + The index of the piece in which the range starts. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +start + The byte offset within that piece where the range starts. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +length + The size of the range, in bytes. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +piece_block +----------- + +Declared in "`libtorrent/piece_block.hpp`__" + + +__ include/libtorrent/piece_block.hpp + + + + + +.. parsed-literal:: + + + struct piece_block + { + **piece_block** (piece\_index\_t p\_index, int b\_index); + **piece_block** () = default; + bool **operator<** (piece\_block const& b) const; + bool **operator==** (piece\_block const& b) const; + bool **operator!=** (piece\_block const& b) const; + + static const piece_block invalid; + piece_index_t piece_index {0}; + int **block_index** = 0; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +info_hash_t +----------- + +Declared in "`libtorrent/info_hash.hpp`__" + + +__ include/libtorrent/info_hash.hpp + +class holding the info-hash of a torrent. It can hold a v1 info-hash +(SHA-1) or a v2 info-hash (SHA-256) or both. + +.. note:: + + If ``has_v2()`` is false then the v1 hash might actually be a truncated + v2 hash + + + + +.. parsed-literal:: + + + struct info_hash_t + { + explicit **info_hash_t** (sha256\_hash h2) noexcept; + explicit **info_hash_t** (sha1\_hash h1) noexcept; + **info_hash_t** () noexcept = default; + **info_hash_t** (sha1\_hash h1, sha256\_hash h2) noexcept; + bool **has_v2** () const; + bool **has** (protocol\_version v) const; + bool **has_v1** () const; + sha1_hash **get** (protocol\_version v) const; + sha1_hash **get_best** () const; + friend bool **operator!=** (info\_hash\_t const& lhs, info\_hash\_t const& rhs); + friend bool **operator==** (info\_hash\_t const& lhs, info\_hash\_t const& rhs) noexcept; + template void **for_each** (F f) const; + bool **operator<** (info\_hash\_t const& o) const; + friend std::ostream& **operator<<** (std\:\:ostream& os, info\_hash\_t const& ih); + + sha1_hash v1; + sha256_hash v2; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +info_hash_t() +............. + +.. parsed-literal:: + + explicit **info_hash_t** (sha256\_hash h2) noexcept; + explicit **info_hash_t** (sha1\_hash h1) noexcept; + **info_hash_t** () noexcept = default; + **info_hash_t** (sha1\_hash h1, sha256\_hash h2) noexcept; + + +The default constructor creates an object that has neither a v1 or v2 +hash. + +For backwards compatibility, make it possible to construct directly +from a v1 hash. This constructor allows *implicit* conversion from a +v1 hash, but the implicitness is deprecated. + + + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +has_v1() has_v2() has() +....................... + +.. parsed-literal:: + + bool **has_v2** () const; + bool **has** (protocol\_version v) const; + bool **has_v1** () const; + + +returns true if the corresponding info hash is present in this +object. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +get() +..... + +.. parsed-literal:: + + sha1_hash **get** (protocol\_version v) const; + + +returns the has for the specified protocol version + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +get_best() +.......... + +.. parsed-literal:: + + sha1_hash **get_best** () const; + + +returns the v2 (truncated) info-hash, if there is one, otherwise +returns the v1 info-hash + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +for_each() +.......... + +.. parsed-literal:: + + template void **for_each** (F f) const; + + +calls the function object ``f`` for each hash that is available. +starting with v1. The signature of ``F`` is:: + + void(sha1_hash, protocol_version); + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +peer_info +--------- + +Declared in "`libtorrent/peer_info.hpp`__" + + +__ include/libtorrent/peer_info.hpp + +holds information and statistics about one peer +that libtorrent is connected to + + + + +.. parsed-literal:: + + + struct peer_info + { + std::string client; + typed_bitfield 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 **outgoing_connection** = 5_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; + static constexpr connection_type_t **standard_bittorrent** = 0_bit; + static constexpr connection_type_t **web_seed** = 1_bit; + static constexpr connection_type_t **http_seed** = 2_bit; + connection_type_t 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; + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +client + A human readable 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. This is UTF-8 + encoded. + + + +.. raw:: html + + + +.. raw:: html + + [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). + + +__ reference-Utility.html#bitfield + +.. raw:: html + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + + +.. raw:: html + + [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 + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +download_queue_time + the time until all blocks in the request queue will be downloaded + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +interesting + **we** are interested in pieces from this peer. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +choked + **we** have choked this peer. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +remote_interested + the peer is interested in **us** + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +remote_choked + the peer has choked **us**. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +supports_extensions + means that this peer supports the + `extension protocol`__. + + __ extension_protocol.html + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +outgoing_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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +local_connection + deprecated synonym for outgoing_connection + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +handshake + The connection is opened, and waiting for the + handshake. Until the handshake is done, the peer + cannot be identified. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +connecting + The connection is in a half-open state (i.e. it is + being connected). + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +seed + This peer is a seed (it has all the pieces). + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +i2p_socket + indicates that this socket is running on top of the + I2P transport. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +utp_socket + indicates that this socket is a uTP socket + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +ssl_socket + indicates that this socket is running on top of an SSL + (TLS) channel + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +rc4_encrypted + this connection is obfuscated with RC4 + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +plaintext_encrypted + the handshake of this connection was obfuscated + with a Diffie-Hellman exchange + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +tracker + The peer was received from the tracker. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +dht + The peer was received from the kademlia DHT. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +pex + The peer was received from the peer exchange + extension. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +lsd + The peer was received from the local service + discovery (The peer is on the local network). + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +resume_data + The peer was added from the fast resume data. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +incoming + we received an incoming connection from this peer + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +source + a combination of flags describing from which sources this peer + was received. A combination of the peer_source_flags_t above. + + + +.. raw:: html + + + + +.. raw:: html + + [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 + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + +payload_up_speed payload_down_speed + The transfer rates of payload data only updated about once per second + + + +.. raw:: html + + + +.. raw:: html + + [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()_ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +queue_bytes + the number of bytes we have requested from this peer, but not yet + received. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + +send_buffer_size used_send_buffer + the number of bytes allocated + and used for the peer's send buffer, respectively. + + + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + +receive_buffer_size used_receive_buffer receive_buffer_watermark + the number of bytes + allocated and used as receive buffer, respectively. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +num_hashfails + the number of pieces this peer has participated in sending us that + turned out to fail the hash check. + + + +.. raw:: html + + + +.. raw:: html + + [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 + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +timed_out_requests + the number of block requests that have timed out, and are still in the + download queue + + + +.. raw:: html + + + +.. raw:: html + + [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 + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +requests_in_buffer + the number of requests messages that are currently in the send buffer + waiting to be sent. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +target_dl_queue_length + the number of requests that is tried to be maintained (this is + typically a function of download speed) + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +standard_bittorrent + Regular bittorrent connection + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +web_seed + HTTP connection using the `BEP 19`_ protocol + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +http_seed + HTTP connection using the `BEP 17`_ protocol + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +connection_type + the kind of connection this peer uses. See connection_type_t. + + + +.. raw:: html + + + +.. raw:: html + + [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``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +pending_disk_read_bytes + number of outstanding bytes to read + from disk + + + +.. raw:: html + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +num_pieces + the number of pieces this peer has. + + + +.. raw:: html + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +progress_ppm + indicates the download progress of the peer in the range [0, 1000000] + (parts per million). + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +ip + 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 + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +bw_idle + The peer is not waiting for any external events to + send or receive data. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +bw_limit + The peer is waiting for the rate limiter. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +bw_disk + The peer is waiting for the disk I/O thread to catch + up writing buffers to disk before downloading more. + + + +.. raw:: html + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +version() +--------- + +Declared in "`libtorrent/version.hpp`__" + + +__ include/libtorrent/version.hpp + +.. parsed-literal:: + + char const* **version** (); + + +returns the libtorrent version as string form in this format: +"..." + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +truncate_files() +---------------- + +Declared in "`libtorrent/truncate.hpp`__" + + +__ include/libtorrent/truncate.hpp + +.. parsed-literal:: + + void **truncate_files** (file\_storage const& fs, std\:\:string const& save\_path, storage\_error& ec); + + +Truncates files larger than specified in the `file_storage`__, saved under +the specified save_path. + + +__ reference-Storage.html#file_storage + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +make_magnet_uri() +----------------- + +Declared in "`libtorrent/magnet_uri.hpp`__" + + +__ include/libtorrent/magnet_uri.hpp + +.. parsed-literal:: + + 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`__. + + + +__ manual-ref.html#magnet-links + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +parse_magnet_uri() +------------------ + +Declared in "`libtorrent/magnet_uri.hpp`__" + + +__ include/libtorrent/magnet_uri.hpp + +.. parsed-literal:: + + add_torrent_params **parse_magnet_uri** (string\_view uri); + void **parse_magnet_uri** (string\_view uri, add\_torrent\_params& p, error\_code& ec); + 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. + + +__ reference-Add_Torrent.html#add_torrent_params + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum connection_type +-------------------- + +Declared in "`libtorrent/peer_connection.hpp`__" + + +__ include/libtorrent/peer_connection.hpp + ++------------+-------+-------------+ +| name | value | description | ++============+=======+=============+ +| bittorrent | 0 | | ++------------+-------+-------------+ +| url_seed | 1 | | ++------------+-------+-------------+ +| http_seed | 2 | | ++------------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum event_t +------------ + +Declared in "`libtorrent/tracker_manager.hpp`__" + + +__ include/libtorrent/tracker_manager.hpp + ++-----------+-------+-------------+ +| name | value | description | ++===========+=======+=============+ +| none | 0 | | ++-----------+-------+-------------+ +| completed | 1 | | ++-----------+-------+-------------+ +| started | 2 | | ++-----------+-------+-------------+ +| stopped | 3 | | ++-----------+-------+-------------+ +| paused | 4 | | ++-----------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum portmap_transport +---------------------- + +Declared in "`libtorrent/portmap.hpp`__" + + +__ include/libtorrent/portmap.hpp + ++--------+-------+------------------------------+ +| name | value | description | ++========+=======+==============================+ +| natpmp | 0 | natpmp can be NAT-PMP or PCP | +| | | | ++--------+-------+------------------------------+ +| upnp | 1 | | ++--------+-------+------------------------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum portmap_protocol +--------------------- + +Declared in "`libtorrent/portmap.hpp`__" + + +__ include/libtorrent/portmap.hpp + ++------+-------+-------------+ +| name | value | description | ++======+=======+=============+ +| none | 0 | | ++------+-------+-------------+ +| tcp | 1 | | ++------+-------+-------------+ +| udp | 2 | | ++------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum protocol_version +--------------------- + +Declared in "`libtorrent/info_hash.hpp`__" + + +__ include/libtorrent/info_hash.hpp + ++------+-------+------------------------------------------------------------+ +| name | value | description | ++======+=======+============================================================+ +| V1 | 0 | The original BitTorrent version, using SHA-1 hashes | +| | | | ++------+-------+------------------------------------------------------------+ +| V2 | 1 | Version 2 of the BitTorrent protocol, using SHA-256 hashes | +| | | | ++------+-------+------------------------------------------------------------+ +| NUM | 2 | | ++------+-------+------------------------------------------------------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum socket_type_t +------------------ + +Declared in "`libtorrent/socket_type.hpp`__" + + +__ include/libtorrent/socket_type.hpp + ++------------+-------+-------------+ +| name | value | description | ++============+=======+=============+ +| tcp | 0 | | ++------------+-------+-------------+ +| socks5 | 1 | | ++------------+-------+-------------+ +| http | 2 | | ++------------+-------+-------------+ +| utp | 3 | | ++------------+-------+-------------+ +| i2p | 4 | | ++------------+-------+-------------+ +| tcp_ssl | 5 | | ++------------+-------+-------------+ +| socks5_ssl | 6 | | ++------------+-------+-------------+ +| http_ssl | 7 | | ++------------+-------+-------------+ +| utp_ssl | 8 | | ++------------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_flags_t +--------------- + +Declared in "`libtorrent/torrent_flags.hpp`__" + + +__ include/libtorrent/torrent_flags.hpp + +.. raw:: html + + + +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. + + + __ reference-Session.html#session + +.. raw:: html + + + +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. + + + +.. raw:: html + + + +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. + + + +.. raw:: html + + + +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. + + + +.. raw:: html + + + +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`__. + + + __ manual-ref.html#queuing + +.. raw:: html + + + +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`__. + + + __ manual-ref.html#queuing + +.. raw:: html + + + +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. + + + __ reference-Add_Torrent.html#add_torrent_params + __ reference-Session.html#session + __ reference-Resume_Data.html#write_resume_data() + +.. raw:: html + + + +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()`__. + + + __ reference-Session.html#post_torrent_updates() + __ reference-Resume_Data.html#write_resume_data() + +.. raw:: html + + + +super_seeding + sets the torrent into super seeding/initial seeding mode. If the torrent + is not a seed, this flag has no effect. + + + +.. raw:: html + + + +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. + + + __ reference-Torrent_Handle.html#set_piece_deadline() + +.. raw:: html + + + +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 + + + __ reference-Add_Torrent.html#add_torrent_params + __ reference-Alerts.html#state_changed_alert + __ reference-Torrent_Handle.html#pause() + __ reference-Alerts.html#alert + +.. raw:: html + + + +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()`__ + + + __ reference-Add_Torrent.html#add_torrent_params + __ reference-Add_Torrent.html#add_torrent_params + __ reference-Resume_Data.html#read_resume_data() + __ reference-Session.html#session + __ reference-Resume_Data.html#write_resume_data() + +.. raw:: html + + + +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()`__ + + + __ reference-Add_Torrent.html#add_torrent_params + __ reference-Add_Torrent.html#add_torrent_params + __ reference-Resume_Data.html#read_resume_data() + __ reference-Session.html#session + __ reference-Resume_Data.html#write_resume_data() + +.. raw:: html + + + +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. + + + __ reference-Torrent_Handle.html#save_resume_data() + __ reference-Resume_Data.html#write_resume_data() + +.. raw:: html + + + +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. + + + +.. raw:: html + + + +disable_lsd + set this flag to disable local service discovery for this torrent. + + + +.. raw:: html + + + +disable_pex + set this flag to disable peer exchange for this torrent. + + + +.. raw:: html + + + +no_verify_files + if this flag is set, the resume data will be assumed to be correct + without validating it against any files on disk. This may be used when + restoring a `session`__ by loading resume data from disk. It will save time + and also delay any hard disk errors until files are actually needed. If + the resume data cannot be trusted, or if a torrent is added for the first + time to some save path that may already have some of the files, this flag + should not be set. + + + __ reference-Session.html#session + +.. raw:: html + + + +all + all torrent flags combined. Can conveniently be used when creating masks + for flags + + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +pex_flags_t +----------- + +Declared in "`libtorrent/pex_flags.hpp`__" + + +__ include/libtorrent/pex_flags.hpp + +.. raw:: html + + + +pex_encryption + the peer supports protocol encryption + + + +.. raw:: html + + + +pex_seed + the peer is a seed + + + +.. raw:: html + + + +pex_utp + the peer supports the uTP, transport protocol over UDP. + + + +.. raw:: html + + + +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 + + + +.. raw:: html + + + +pex_lt_v2 + protocol v2 + this is not a standard flag, it is only used internally + + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +int +--- + +Declared in "`libtorrent/version.hpp`__" + + +__ include/libtorrent/version.hpp + +.. raw:: html + + + +version_major + the major, minor and tiny versions of libtorrent + + + +.. raw:: html + + + +version_minor + the major, minor and tiny versions of libtorrent + + + +.. raw:: html + + + +version_tiny + the major, minor and tiny versions of libtorrent + + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +char const* +----------- + +Declared in "`libtorrent/version.hpp`__" + + +__ include/libtorrent/version.hpp + +.. raw:: html + + + +version_str + the libtorrent version in string form + + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +std::uint64_t +------------- + +Declared in "`libtorrent/version.hpp`__" + + +__ include/libtorrent/version.hpp + +.. raw:: html + + + +version_revision + the git commit of this libtorrent version + + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +download_priority_t +------------------- + +Declared in "`libtorrent/download_priority.hpp`__" + + +__ include/libtorrent/download_priority.hpp + +.. raw:: html + + + +dont_download + Don't download the file or piece. Partial pieces may still be downloaded when + setting file priorities. + + + +.. raw:: html + + + +default_priority + The default priority for files and pieces. + + + +.. raw:: html + + + +low_priority + The lowest priority for files and pieces. + + + +.. raw:: html + + + +top_priority + The highest priority for files and pieces. + + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking + +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()); + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +create_torrent +-------------- + +Declared in "`libtorrent/create_torrent.hpp`__" + + +__ include/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()`__. + + +__ reference-Create_Torrents.html#generate() +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#bencode() + + +.. parsed-literal:: + + + struct create_torrent + { + explicit **create_torrent** (torrent\_info const& ti); + explicit **create_torrent** (file\_storage& fs, int piece\_size = 0 + , create\_flags\_t flags = {}); + entry **generate** () const; + file_storage const& **files** () const; + void **set_comment** (char const\* str); + void **set_creator** (char const\* str); + void **set_creation_date** (std\:\:time\_t timestamp); + void **set_hash** (piece\_index\_t index, sha1\_hash const& h); + void **set_hash2** (file\_index\_t file, piece\_index\_t\:\:diff\_type piece, sha256\_hash const& h); + void **add_url_seed** (string\_view url); + void **add_http_seed** (string\_view url); + void **add_node** (std\:\:pair node); + void **add_tracker** (string\_view url, int tier = 0); + void **set_root_cert** (string\_view cert); + void **set_priv** (bool p); + bool **priv** () const; + bool **is_v1_only** () const; + bool **is_v2_only** () const; + int **num_pieces** () const; + int **piece_length** () const; + int **piece_size** (piece\_index\_t i) const; + void **add_collection** (string\_view c); + void **add_similar_torrent** (sha1\_hash ih); + + static constexpr create_flags_t **modification_time** = 2_bit; + static constexpr create_flags_t **symlinks** = 3_bit; + static constexpr create_flags_t **v2_only** = 5_bit; + static constexpr create_flags_t **v1_only** = 6_bit; + static constexpr create_flags_t **canonical_files** = 7_bit; + static constexpr create_flags_t **no_attributes** = 8_bit; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +create_torrent() +................ + +.. parsed-literal:: + + explicit **create_torrent** (torrent\_info const& ti); + explicit **create_torrent** (file\_storage& fs, int piece\_size = 0 + , create\_flags\_t flags = {}); + + +The ``piece_size`` is the size of each piece in bytes. It must be a +power of 2 and a minimum of 16 kiB. If a piece size of 0 is +specified, a piece_size will be set automatically. + +The `file_storage`__ (``fs``) parameter defines the files, sizes and +their properties for the torrent to be created. Set this up first, +before passing it to the `create_torrent`__ constructor. + +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. + +.. warning:: + The `file_storage`__ and `torrent_info`__ objects must stay alive for the + entire duration of the `create_torrent`__ object. + +The ``flags`` arguments specifies options for the torrent creation. It can +be any combination of the flags defined by create_flags_t. + + +__ reference-Storage.html#file_storage +__ reference-Create_Torrents.html#create_torrent +__ reference-Create_Torrents.html#generate() +__ reference-Create_Torrents.html#create_torrent +__ reference-Create_Torrents.html#set_hash() +__ reference-Storage.html#file_storage +__ reference-Torrent_Info.html#torrent_info +__ reference-Create_Torrents.html#create_torrent + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +generate() +.......... + +.. parsed-literal:: + + 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. + +Whether the resulting torrent object is v1, v2 or hybrid depends on +whether any of the v1_only or v2_only flags were set on the +constructor. If neither were set, the resulting torrent depends on +which hashes were set. If both v1 and v2 hashes were set, a hybrid +torrent is created. + +Any failure will cause this function to throw system_error, with an +appropriate error message. These are the reasons this call may throw: + +* the file storage has 0 files +* the total size of the file storage is 0 bytes (i.e. it only has + empty files) +* not all v1 hashes (`set_hash()`__) and not all v2 hashes (`set_hash2()`__) + were set +* for v2 torrents, you may not have a directory with the same name as + a file. If that's encountered in the file storage, `generate()`__ + fails. + + +__ reference-Bencoding.html#bencode() +__ reference-Create_Torrents.html#set_hash() +__ reference-Create_Torrents.html#set_hash2() +__ reference-Create_Torrents.html#generate() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +files() +....... + +.. parsed-literal:: + + file_storage const& **files** () const; + + +returns an immutable reference to the `file_storage`__ used to create +the torrent from. + + +__ reference-Storage.html#file_storage + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +set_comment() +............. + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +set_creator() +............. + +.. parsed-literal:: + + void **set_creator** (char const\* str); + + +Sets the creator of the torrent. The string ``str`` should be utf-8 encoded. +This is optional. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +set_creation_date() +................... + +.. parsed-literal:: + + void **set_creation_date** (std\:\:time\_t timestamp); + + +sets the "creation time" field. Defaults to the system clock at the +time of construction of the `create_torrent`__ object. The timestamp is +specified in seconds, posix time. If the creation date is set to 0, +the "creation date" field will be omitted from the generated torrent. + + +__ reference-Create_Torrents.html#create_torrent + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +set_hash() +.......... + +.. parsed-literal:: + + 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()`__. +A SHA-1 hash of all zeros is internally used to indicate a hash that +has not been set. Setting such hash will not be considered set when +calling `generate()`__. +This function will throw ``std::system_error`` if it is called on an +object constructed with the v2_only flag. + + +__ reference-Create_Torrents.html#set_piece_hashes() +__ reference-Create_Torrents.html#generate() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +set_hash2() +........... + +.. parsed-literal:: + + void **set_hash2** (file\_index\_t file, piece\_index\_t\:\:diff\_type piece, sha256\_hash const& h); + + +sets the bittorrent v2 hash for file `file` of the piece `piece`. +`piece` is relative to the first piece of the file, starting at 0. The +first piece in the file can be computed with +`file_storage::file_index_at_piece()`__. +The hash, `h`, is the root of the merkle tree formed by the piece's +16 kiB blocks. Note that piece sizes must be powers-of-2, so all +per-piece merkle trees are complete. +A SHA-256 hash of all zeros is internally used to indicate a hash +that has not been set. Setting such hash will not be considered set +when calling `generate()`__. +This function will throw ``std::system_error`` if it is called on an +object constructed with the v1_only flag. + + +__ reference-Storage.html#file_index_at_piece() +__ reference-Create_Torrents.html#generate() + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +add_http_seed() add_url_seed() +.............................. + +.. parsed-literal:: + + void **add_url_seed** (string\_view url); + void **add_http_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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +add_node() +.......... + +.. parsed-literal:: + + void **add_node** (std\:\:pair 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +add_tracker() +............. + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +set_root_cert() +............... + +.. parsed-literal:: + + void **set_root_cert** (string\_view cert); + + +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. + + +__ manual-ref.html#ssl-torrents + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +set_priv() priv() +................. + +.. parsed-literal:: + + void **set_priv** (bool p); + bool **priv** () const; + + +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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +num_pieces() +............ + +.. parsed-literal:: + + int **num_pieces** () const; + + +returns the number of pieces in the associated `file_storage`__ object. + + +__ reference-Storage.html#file_storage + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +piece_size() piece_length() +........................... + +.. parsed-literal:: + + 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`__. + + +__ reference-Storage.html#file_storage + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +add_collection() add_similar_torrent() +...................................... + +.. parsed-literal:: + + void **add_collection** (string\_view c); + void **add_similar_torrent** (sha1\_hash ih); + + +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`_. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +v2_only + Do not generate v1 metadata. The resulting torrent will only be usable by + clients which support v2. This requires setting all v2 hashes, with + `set_hash2()`__ before calling `generate()`__. Setting v1 hashes (with + `set_hash()`__) is an error with this flag set. + + +__ reference-Create_Torrents.html#set_hash2() +__ reference-Create_Torrents.html#generate() +__ reference-Create_Torrents.html#set_hash() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +v1_only + do not generate v2 metadata or enforce v2 alignment and padding rules + this is mainly for tests, not recommended for production use. This + requires setting all v1 hashes, with `set_hash()`__, before calling + `generate()`__. Setting v2 hashes (with `set_hash2()`__) is an error with + this flag set. + + +__ reference-Create_Torrents.html#set_hash() +__ reference-Create_Torrents.html#generate() +__ reference-Create_Torrents.html#set_hash2() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +canonical_files + This flag only affects v1-only torrents, and is only relevant + together with the v1_only_flag. This flag will force the + same file order and padding as a v2 (or hybrid) torrent would have. + It has the effect of ordering files and inserting pad files to align + them with piece boundaries. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +no_attributes + passing this flag to `add_files()`__ will ignore file attributes (such as + executable or hidden) when adding the files to the file storage. + Since not all filesystems and operating systems support all file + attributes the resulting torrent may differ depending on where it's + created. If it's important for torrents to be created consistently + across systems, this flag should be set. + + +__ reference-Create_Torrents.html#add_files() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +add_files() +----------- + +Declared in "`libtorrent/create_torrent.hpp`__" + + +__ include/libtorrent/create_torrent.hpp + +.. parsed-literal:: + + void **add_files** (file\_storage& fs, std\:\:string const& file + , std\:\:function p, create\_flags\_t flags = {}); + void **add_files** (file\_storage& fs, std\:\:string const& file + , 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: + +.. 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. + + +__ reference-Storage.html#file_storage + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +set_piece_hashes() +------------------ + +Declared in "`libtorrent/create_torrent.hpp`__" + + +__ include/libtorrent/create_torrent.hpp + +.. parsed-literal:: + + inline void **set_piece_hashes** (create\_torrent& t, std\:\:string const& p + , settings\_interface const& settings + , std\:\:function const& f); + void **set_piece_hashes** (create\_torrent& t, std\:\:string const& p + , settings\_interface const& settings + , std\:\:function const& f, error\_code& ec); + 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 const& f, error\_code& ec); + inline void **set_piece_hashes** (create\_torrent& t, std\:\:string const& p + , std\:\:function const& f); + void **set_piece_hashes** (create\_torrent& t, std\:\:string const& p + , settings\_interface const& settings, disk\_io\_constructor\_type disk\_io + , std\:\:function const& f, error\_code& ec); + 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: + +.. code:: c++ + + void Fun(piece_index_t); + +The overloads taking a `settings_pack`__ may be used to configure the +underlying disk access. Such as ``settings_pack::aio_threads``. + +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. + + +__ reference-Settings.html#settings_pack + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking + +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. + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +entry +----- + +Declared in "`libtorrent/entry.hpp`__" + + +__ reference-Alerts.html#int +__ reference-Session.html#session +__ reference-Bdecoding.html#bdecode() +__ reference-Bdecoding.html#bdecode_node +__ reference-Bdecoding.html#bdecode_node +__ reference-Bencoding.html#entry +__ reference-Bdecoding.html#bdecode_node +__ include/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. + + + + +.. parsed-literal:: + + + class entry + { + data_type **type** () const; + **entry** (list\_type); + **entry** (dictionary\_type); + **entry** (span); + **entry** (preformatted\_type); + **entry** (integer\_type); + **entry** (U v); + **entry** (data\_type t); + **entry** (bdecode\_node const& n); + entry& **operator=** (list\_type) &; + entry& **operator=** (integer\_type) &; + entry& **operator=** (span) &; + entry& **operator=** (entry const&) &; + entry& **operator=** (preformatted\_type) &; + entry& **operator=** (dictionary\_type) &; + entry& **operator=** (entry&&) & noexcept; + entry& **operator=** (bdecode\_node const&) &; + entry& **operator=** (U v) &; + dictionary_type& **dict** (); + integer_type const& **integer** () const; + list_type& **list** (); + integer_type& **integer** (); + preformatted_type& **preformatted** (); + list_type const& **list** () const; + string_type const& **string** () const; + dictionary_type const& **dict** () const; + preformatted_type const& **preformatted** () const; + string_type& **string** (); + void **swap** (entry& e); + entry& **operator[]** (string\_view key); + entry const& **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, + }; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +type() +...... + +.. parsed-literal:: + + data_type **type** () const; + + +returns the concrete type of the `entry`__ + + +__ reference-Bencoding.html#entry + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +entry() +....... + +.. parsed-literal:: + + **entry** (list\_type); + **entry** (dictionary\_type); + **entry** (span); + **entry** (preformatted\_type); + **entry** (integer\_type); + + +constructors directly from a specific type. +The content of the argument is copied into the +newly constructed `entry`__ + + +__ reference-Bencoding.html#entry + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +entry() +....... + +.. parsed-literal:: + + **entry** (data\_type t); + + +construct an empty `entry`__ of the specified type. +see `data_type`__ enum. + + +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#data_type + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +entry() +....... + +.. parsed-literal:: + + **entry** (bdecode\_node const& n); + + +construct from `bdecode_node`__ parsed form (see `bdecode()`__) + + +__ reference-Bdecoding.html#bdecode_node +__ reference-Bdecoding.html#bdecode() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +operator=() +........... + +.. parsed-literal:: + + entry& **operator=** (list\_type) &; + entry& **operator=** (integer\_type) &; + entry& **operator=** (span) &; + entry& **operator=** (entry const&) &; + entry& **operator=** (preformatted\_type) &; + entry& **operator=** (dictionary\_type) &; + entry& **operator=** (entry&&) & noexcept; + entry& **operator=** (bdecode\_node const&) &; + + +copies the structure of the right hand side into this +`entry`__. + + +__ reference-Bencoding.html#entry + +.. raw:: html + + + + + + + +.. raw:: html + + [report issue] + + + +integer() preformatted() string() list() dict() +............................................... + +.. parsed-literal:: + + dictionary_type& **dict** (); + integer_type const& **integer** () const; + list_type& **list** (); + integer_type& **integer** (); + preformatted_type& **preformatted** (); + list_type const& **list** () const; + string_type const& **string** () const; + dictionary_type const& **dict** () const; + preformatted_type const& **preformatted** () const; + string_type& **string** (); + + +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. + + +__ reference-Torrent_Info.html#torrent_info + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +swap() +...... + +.. parsed-literal:: + + void **swap** (entry& e); + + +swaps the content of *this* with ``e``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +operator[]() +............ + +.. parsed-literal:: + + entry& **operator[]** (string\_view key); + entry const& **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``. + + +__ reference-Bencoding.html#entry + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +find_key() +.......... + +.. parsed-literal:: + + 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. + + +__ reference-Bencoding.html#entry + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +to_string() +........... + +.. parsed-literal:: + + std::string **to_string** (bool single\_line = false) const; + + +returns a pretty-printed string representation +of the bencoded structure, with JSON-style syntax + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum data_type +.............. + +Declared in "`libtorrent/entry.hpp`__" + + +__ include/libtorrent/entry.hpp + ++----------------+-------+-------------+ +| name | value | description | ++================+=======+=============+ +| int_t | 0 | | ++----------------+-------+-------------+ +| string_t | 1 | | ++----------------+-------+-------------+ +| list_t | 2 | | ++----------------+-------+-------------+ +| dictionary_t | 3 | | ++----------------+-------+-------------+ +| undefined_t | 4 | | ++----------------+-------+-------------+ +| preformatted_t | 5 | | ++----------------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +bencode() +--------- + +Declared in "`libtorrent/bencode.hpp`__" + + +__ include/libtorrent/bencode.hpp + +.. parsed-literal:: + + template 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 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 + + +__ reference-Bencoding.html#entry + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +operator<<() +------------ + +Declared in "`libtorrent/entry.hpp`__" + + +__ include/libtorrent/entry.hpp + +.. parsed-literal:: + + inline std::ostream& **operator<<** (std\:\:ostream& os, const entry& e); + + +prints the bencoded structure to the ostream as a JSON-style structure. + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +peer_class_type_filter +---------------------- + +Declared in "`libtorrent/peer_class_type_filter.hpp`__" + + +__ include/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). + + + + +.. parsed-literal:: + + + struct peer_class_type_filter + { + void **add** (socket\_type\_t const st, peer\_class\_t const peer\_class); + void **remove** (socket\_type\_t const st, peer\_class\_t const peer\_class); + void **allow** (socket\_type\_t const st, peer\_class\_t const peer\_class); + void **disallow** (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); + friend bool **operator==** (peer\_class\_type\_filter const& lhs + , peer\_class\_type\_filter const& rhs); + + enum socket_type_t + { + tcp_socket, + utp_socket, + ssl_tcp_socket, + ssl_utp_socket, + i2p_socket, + num_socket_types, + }; + }; + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +remove() add() +.............. + +.. parsed-literal:: + + void **add** (socket\_type\_t const st, peer\_class\_t const peer\_class); + void **remove** (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. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +allow() disallow() +.................. + +.. parsed-literal:: + + void **allow** (socket\_type\_t const st, peer\_class\_t const peer\_class); + void **disallow** (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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +apply() +....... + +.. parsed-literal:: + + 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``). + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum socket_type_t +.................. + +Declared in "`libtorrent/peer_class_type_filter.hpp`__" + + +__ include/libtorrent/peer_class_type_filter.hpp + ++------------------+-------+---------------------------------------------------+ +| name | value | description | ++==================+=======+===================================================+ +| tcp_socket | 0 | these match the socket types from socket_type.hpp | +| | | shifted one down | +| | | | ++------------------+-------+---------------------------------------------------+ +| utp_socket | 1 | | ++------------------+-------+---------------------------------------------------+ +| ssl_tcp_socket | 2 | | ++------------------+-------+---------------------------------------------------+ +| ssl_utp_socket | 3 | | ++------------------+-------+---------------------------------------------------+ +| i2p_socket | 4 | | ++------------------+-------+---------------------------------------------------+ +| num_socket_types | 5 | | ++------------------+-------+---------------------------------------------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +peer_class_info +--------------- + +Declared in "`libtorrent/peer_class.hpp`__" + + +__ include/libtorrent/peer_class.hpp + +holds settings for a peer class. Used in `set_peer_class()`__ and +`get_peer_class()`__ calls. + + +__ reference-Session.html#set_peer_class() +__ reference-Session.html#get_peer_class() + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +label + not used by libtorrent. It's intended as a potentially user-facing + identifier of this peer class. + + + +.. raw:: html + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + + +.. raw:: html + + [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. + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking + +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. + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_routing_bucket +------------------ + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ reference-Session.html#pop_alerts() +__ reference-Session.html#session +__ reference-Session.html#pop_alerts() +__ reference-Settings.html#alert_mask +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert_category_t +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Torrent_Handle.html#save_resume_data() +__ reference-Session.html#post_session_stats() +__ reference-Alerts.html#alert +__ reference-Alerts.html#torrent_alert +__ reference-Alerts.html#tracker_alert +__ reference-Session.html#pop_alerts() +__ reference-Session.html#pop_alerts() +__ reference-Alerts.html#alert +__ reference-Session.html#pop_alerts() +__ reference-Session.html#pop_alerts() +__ include/libtorrent/alert_types.hpp + +struct to hold information about a single DHT routing table bucket + + + + +.. parsed-literal:: + + + struct dht_routing_bucket + { + int num_nodes; + int num_replacements; + int last_active; + }; + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + +num_nodes num_replacements + the total number of nodes and replacement nodes + in the routing table + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +last_active + number of seconds since last activity + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_alert +------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + +Note that by the time the client receives a `torrent_alert`__, its +``handle`` member may be invalid. + + +__ reference-Alerts.html#torrent_alert + + +.. parsed-literal:: + + + struct torrent_alert : alert + { + std::string **message** () const override; + char const* **torrent_name** () const; + + torrent_handle handle; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +message() +......... + +.. parsed-literal:: + + std::string **message** () const override; + + +returns the message associated with this `alert`__ + + +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +handle + The `torrent_handle`__ pointing to the torrent this + `alert`__ is associated with. + + +__ reference-Torrent_Handle.html#torrent_handle +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +peer_alert +---------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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``. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct peer_alert : torrent_alert + { + std::string **message** () const override; + + aux::noexcept_movable endpoint; + peer_id pid; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +endpoint + The peer's IP address and port. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +pid + the peer ID, if known. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +tracker_alert +------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#torrent_alert + + +.. parsed-literal:: + + + struct tracker_alert : torrent_alert + { + std::string **message** () const override; + char const* **tracker_url** () const; + + aux::noexcept_movable local_endpoint; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +tracker_url() +............. + +.. parsed-literal:: + + char const* **tracker_url** () const; + + +returns a 0-terminated string of the tracker's URL + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +local_endpoint + endpoint of the listen interface being announced + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_removed_alert +--------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +The ``torrent_removed_alert`` is posted whenever a torrent is removed. Since +the torrent handle in its base class will usually be invalid (since the torrent +is already removed) it has the info hash as a member, to identify it. +It's posted when the ``alert_category::status`` bit is set in the alert_mask. + +Note that the ``handle`` remains valid for some time after +`torrent_removed_alert`__ is posted, as long as some internal libtorrent +task (such as an I/O task) refers to it. Additionally, other alerts like +`save_resume_data_alert`__ may be posted after `torrent_removed_alert`__. +To synchronize on whether the torrent has been removed or not, call +`torrent_handle::in_session()`__. This will return true before +`torrent_removed_alert`__ is posted, and false afterward. + +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. + + +__ reference-Alerts.html#torrent_removed_alert +__ reference-Alerts.html#save_resume_data_alert +__ reference-Alerts.html#torrent_removed_alert +__ reference-Torrent_Handle.html#in_session() +__ reference-Alerts.html#torrent_removed_alert + + +.. parsed-literal:: + + + struct torrent_removed_alert final : torrent_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::status; + info_hash_t info_hashes; + client_data_t userdata; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +userdata + '`userdata`` as set in `add_torrent_params`__ at torrent creation. + This can be used to associate this torrent with related data + in the client application more efficiently than info_hashes. + + +__ reference-Add_Torrent.html#add_torrent_params + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +read_piece_alert +---------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Torrent_Handle.html#read_piece() +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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 const buffer; + piece_index_t const piece; + int const size; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_completed_alert +-------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + + + +.. parsed-literal:: + + + struct file_completed_alert final : torrent_alert + { + std::string **message** () const override; + + file_index_t const index; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +index + refers to the index of the file that completed. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_renamed_alert +------------------ + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +This is posted as a response to a `torrent_handle::rename_file()`__ call, if the rename +operation succeeds. + + +__ reference-Torrent_Handle.html#rename_file() + + +.. parsed-literal:: + + + struct file_renamed_alert final : torrent_alert + { + std::string **message** () const override; + char const* **old_name** () const; + char const* **new_name** () const; + + static constexpr alert_category_t **static_category** = alert_category::storage; + file_index_t const index; + }; + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +new_name() old_name() +..................... + +.. parsed-literal:: + + char const* **old_name** () const; + char const* **new_name** () const; + + +returns the new and previous file name, respectively. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +index + refers to the index of the file that was renamed, + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_rename_failed_alert +------------------------ + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +This is posted as a response to a `torrent_handle::rename_file()`__ call, if the rename +operation failed. + + +__ reference-Torrent_Handle.html#rename_file() + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +performance_alert +----------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum performance_warning_t +.......................... + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + ++---------------------------------------+-------+-------------------------------------------------------------------------------------------------+ +| name | value | description | ++=======================================+=======+=================================================================================================+ +| outstanding_disk_buffer_limit_reached | 0 | 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_request_limit_reached | 1 | 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. | +| | | | ++---------------------------------------+-------+-------------------------------------------------------------------------------------------------+ +| upload_limit_too_low | 2 | 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. | +| | | | ++---------------------------------------+-------+-------------------------------------------------------------------------------------------------+ +| download_limit_too_low | 3 | 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. | +| | | | ++---------------------------------------+-------+-------------------------------------------------------------------------------------------------+ +| send_buffer_watermark_too_low | 4 | 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``. | +| | | | ++---------------------------------------+-------+-------------------------------------------------------------------------------------------------+ +| too_many_optimistic_unchoke_slots | 5 | 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_high_disk_queue_limit | 6 | 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``. | +| | | | ++---------------------------------------+-------+-------------------------------------------------------------------------------------------------+ +| aio_limit_reached | 7 | | ++---------------------------------------+-------+-------------------------------------------------------------------------------------------------+ +| deprecated_bittyrant_with_no_uplimit | 8 | | ++---------------------------------------+-------+-------------------------------------------------------------------------------------------------+ +| too_few_outgoing_ports | 9 | 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_file_descriptors | 10 | | ++---------------------------------------+-------+-------------------------------------------------------------------------------------------------+ +| num_warnings | 11 | | ++---------------------------------------+-------+-------------------------------------------------------------------------------------------------+ + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +state_changed_alert +------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +Generated whenever a torrent changes its state. + + + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +state + the new state of the torrent. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +prev_state + the previous state. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +tracker_error_alert +------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct tracker_error_alert final : tracker_alert + { + std::string **message** () const override; + char const* **failure_reason** () const; + + static constexpr alert_category_t **static_category** = alert_category::tracker | alert_category::error; + int const times_in_row; + error_code const error; + operation_t op; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +failure_reason() +................ + +.. parsed-literal:: + + char const* **failure_reason** () const; + + +if the tracker sent a "failure reason" string, it will be returned +here. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +times_in_row + This member says how many times in a row this tracker has failed. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + the error code indicating why the tracker announce failed. If it is + is ``lt::errors::tracker_failure`` the `failure_reason()`__ might contain + a more detailed description of why the tracker rejected the request. + HTTP status codes indicating errors are also set in this field. + + +__ reference-Alerts.html#failure_reason() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +tracker_warning_alert +--------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +warning_message() +................. + +.. parsed-literal:: + + char const* **warning_message** () const; + + +the message associated with this warning + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +scrape_reply_alert +------------------ + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +This `alert`__ is generated when a scrape request succeeds. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + +incomplete complete + the data returned in the scrape response. These numbers + may be -1 if the response was malformed. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +scrape_failed_alert +------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +error_message() +............... + +.. parsed-literal:: + + char const* **error_message** () const; + + +if the error indicates there is an associated message, this returns +that message. Otherwise and empty string. + + + +.. raw:: html + + + +.. raw:: html + + [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()``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +tracker_reply_alert +------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_reply_alert +--------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +tracker_announce_alert +---------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct tracker_announce_alert final : tracker_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::tracker; + event_t const event; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +event + specifies what event was sent to the tracker. See `event_t`__. + + +__ reference-Core.html#event_t + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +hash_failed_alert +----------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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`__. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +peer_ban_alert +-------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct peer_ban_alert final : peer_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::peer; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +peer_unsnubbed_alert +-------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct peer_unsnubbed_alert final : peer_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::peer; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +peer_snubbed_alert +------------------ + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +This `alert`__ is generated when a peer is snubbed, when it stops sending data when we request +it. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct peer_snubbed_alert final : peer_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::peer; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +peer_error_alert +---------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +op + a 0-terminated string of the low-level operation that failed, or nullptr if + there was no low level disk operation. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + tells you what error caused this `alert`__. + + +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +peer_connect_alert +------------------ + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +This `alert`__ is posted every time an incoming peer connection both +successfully passes the protocol handshake and is associated with a +torrent, or an outgoing peer connection attempt succeeds. For arbitrary +incoming connections, see `incoming_connection_alert`__. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#incoming_connection_alert + + +.. parsed-literal:: + + + struct peer_connect_alert final : peer_alert + { + std::string **message** () const override; + + enum direction_t + { + in, + out, + }; + + static constexpr alert_category_t **static_category** = alert_category::connect; + direction_t direction; + socket_type_t socket_type; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum direction_t +................ + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + ++------+-------+-------------+ +| name | value | description | ++======+=======+=============+ +| in | 0 | | ++------+-------+-------------+ +| out | 1 | | ++------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +direction + Tells you if the peer was incoming or outgoing + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +peer_disconnected_alert +----------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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`__ ). + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#peer_error_alert + + +.. parsed-literal:: + + + struct peer_disconnected_alert final : peer_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::connect; + socket_type_t const socket_type; + operation_t const op; + error_code const error; + close_reason_t const reason; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +socket_type + the kind of socket this peer was connected over + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +op + the operation or level where the error occurred. Specified as an + value from the `operation_t`__ enum. Defined in operations.hpp. + + +__ reference-Alerts.html#operation_t + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + tells you what error caused peer to disconnect. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +reason + the reason the peer disconnected (if specified) + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +invalid_request_alert +--------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Core.html#peer_request + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +request + the request we received from the peer + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +we_have + true if we have this piece + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +peer_interested + true if the peer indicated that it was interested to download before + sending the request + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +withheld + if this is true, the peer is not allowed to download this piece because + of super-seeding rules. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_finished_alert +---------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Torrent_Handle.html#torrent_handle + + +.. parsed-literal:: + + + struct torrent_finished_alert final : torrent_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::status; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +piece_finished_alert +-------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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 + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#torrent_alert +__ reference-Torrent_Handle.html#torrent_handle +__ reference-Torrent_Handle.html#have_piece() + + +.. parsed-literal:: + + + struct piece_finished_alert final : torrent_alert + { + std::string **message** () const override; + + piece_index_t const piece_index; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +piece_index + the index of the piece that finished + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +request_dropped_alert +--------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +This `alert`__ is generated when a peer rejects or ignores a piece request. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct request_dropped_alert final : peer_alert + { + std::string **message** () const override; + + int const block_index; + piece_index_t const piece_index; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +block_timeout_alert +------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +This `alert`__ is generated when a block request times out. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct block_timeout_alert final : peer_alert + { + std::string **message** () const override; + + int const block_index; + piece_index_t const piece_index; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +block_finished_alert +-------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +This `alert`__ is generated when a block request receives a response. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct block_finished_alert final : peer_alert + { + std::string **message** () const override; + + int const block_index; + piece_index_t const piece_index; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +block_downloading_alert +----------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +This `alert`__ is generated when a block request is sent to a peer. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct block_downloading_alert final : peer_alert + { + std::string **message** () const override; + + int const block_index; + piece_index_t const piece_index; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +unwanted_block_alert +-------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +This `alert`__ is generated when a block is received that was not requested or +whose request timed out. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +storage_moved_alert +------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + + + +.. parsed-literal:: + + + struct storage_moved_alert final : torrent_alert + { + std::string **message** () const override; + char const* **old_path** () const; + char const* **storage_path** () const; + + static constexpr alert_category_t **static_category** = alert_category::storage; + }; + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +old_path() storage_path() +......................... + +.. parsed-literal:: + + char const* **old_path** () const; + char const* **storage_path** () const; + + +the path the torrent was moved to and from, respectively. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +storage_moved_failed_alert +-------------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +The ``storage_moved_failed_alert`` is generated when an attempt to move the storage, +via `torrent_handle::move_storage()`__, fails. + + +__ reference-Torrent_Handle.html#move_storage() + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_path() +........... + +.. parsed-literal:: + + char const* **file_path** () const; + + +If the error happened for a specific file, this returns its path. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +op + this indicates what underlying operation caused the error + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_deleted_alert +--------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +This `alert`__ is generated when a request to delete the files of a torrent complete. + +This `alert`__ is posted in the ``alert_category::storage`` category, and that bit +needs to be set in the alert_mask. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct torrent_deleted_alert final : torrent_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::storage; + info_hash_t info_hashes; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +info_hashes + 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_hashes`` member + is hence the main way of identifying which torrent just completed the delete. + + +__ reference-Torrent_Handle.html#torrent_handle +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_delete_failed_alert +--------------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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 + + +__ reference-Alerts.html#alert +__ reference-Session.html#session + + +.. parsed-literal:: + + + 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; + info_hash_t info_hashes; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + tells you why it failed. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +info_hashes + the info hash of the torrent whose files failed to be deleted + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +save_resume_data_alert +---------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [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()`__. + + +__ reference-Session.html#add_torrent() +__ reference-Session.html#async_add_torrent() +__ reference-Resume_Data.html#write_resume_data() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +save_resume_data_failed_alert +----------------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + the error code from the resume_data failure + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_paused_alert +-------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct torrent_paused_alert final : torrent_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::status; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_resumed_alert +--------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Torrent_Handle.html#resume() + + +.. parsed-literal:: + + + struct torrent_resumed_alert final : torrent_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::status; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_checked_alert +--------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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 + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct torrent_checked_alert final : torrent_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::status; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +url_seed_alert +-------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +This `alert`__ is generated when a HTTP seed name lookup fails. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +server_url() +............ + +.. parsed-literal:: + + char const* **server_url** () const; + + +the URL the error is associated with + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +error_message() +............... + +.. parsed-literal:: + + char const* **error_message** () const; + + +in case the web server sent an error message, this function returns +it. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + the error the web seed encountered. If this is not set, the server + sent an error message, call ``error_message()``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_error_alert +---------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +filename() +.......... + +.. parsed-literal:: + + char const* **filename** () const; + + +the file that experienced the error + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + the error code describing the error. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +op + indicates which underlying operation caused the error + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +metadata_failed_alert +--------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + indicates what failed when parsing the metadata. This error is + what's returned from lazy_bdecode(). + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +metadata_received_alert +----------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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: + +.. code:: c++ + + torrent_handle h = alert->handle(); + 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_hashes().get_best().to_string()) + ".torrent").c_str(), "wb+"); + if (f) { + fwrite(&buffer[0], 1, buffer.size(), f); + fclose(f); + } + + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Session.html#session + + +.. parsed-literal:: + + + struct metadata_received_alert final : torrent_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::status; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +udp_error_alert +--------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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`__. + + +__ reference-Alerts.html#alert +__ reference-Session.html#session + + +.. parsed-literal:: + + + struct udp_error_alert final : alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::error; + aux::noexcept_movable endpoint; + operation_t operation; + error_code const error; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +endpoint + the source address associated with the error (if any) + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +operation + the operation that failed + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + the error code describing the error + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +external_ip_alert +----------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct external_ip_alert final : alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::status; + aux::noexcept_movable
    external_address; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +external_address + the IP address that is believed to be our external IP + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +listen_failed_alert +------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Session.html#session +__ reference-Alerts.html#alert +__ reference-Alerts.html#listen_failed_alert + + +.. parsed-literal:: + + + struct listen_failed_alert final : alert + { + 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 address; + int const port; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +listen_interface() +.................. + +.. parsed-literal:: + + char const* **listen_interface** () const; + + +the network device libtorrent attempted to listen on, or the IP address + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + the error the system returned + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +op + the underlying operation that failed + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +socket_type + the type of listen socket this `alert`__ refers to. + + +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +address + the address libtorrent attempted to listen on + see `alert`__ documentation for validity of this value + + +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +port + the port libtorrent attempted to listen on + see `alert`__ documentation for validity of this value + + +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +listen_succeeded_alert +---------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct listen_succeeded_alert final : alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::status; + aux::noexcept_movable address; + int const port; + lt::socket_type_t const socket_type; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +address + the address libtorrent ended up listening on. This address + refers to the local interface. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +port + the port libtorrent ended up listening on. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +socket_type + the type of listen socket this `alert`__ refers to. + + +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +portmap_error_alert +------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + aux::noexcept_movable
    local_address; + error_code const error; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +mapping + refers to the mapping index of the port map that failed, i.e. + the index returned from add_mapping(). + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +map_transport + UPnP or NAT-PMP + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +local_address + the local network the port mapper is running on + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + tells you what failed. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +portmap_alert +------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + aux::noexcept_movable
    local_address; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +mapping + refers to the mapping index of the port map that failed, i.e. + the index returned from add_mapping(). + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +external_port + the external port allocated for the mapping. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +local_address + the local network the port mapper is running on + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +portmap_log_alert +----------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + aux::noexcept_movable
    local_address; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +log_message() +............. + +.. parsed-literal:: + + char const* **log_message** () const; + + +the message associated with this log line + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +local_address + the local network the port mapper is running on + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +fastresume_rejected_alert +------------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Session.html#add_torrent() + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_path() +........... + +.. parsed-literal:: + + char const* **file_path** () const; + + +If the error happened to a specific file, this returns the path to it. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +op + the underlying operation that failed + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +peer_blocked_alert +------------------ + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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) + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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, + ssrf_mitigation, + }; + + static constexpr alert_category_t **static_category** = alert_category::ip_block; + int const reason; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum reason_t +............. + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + ++-------------------------+-------+-------------+ +| name | value | description | ++=========================+=======+=============+ +| ip_filter | 0 | | ++-------------------------+-------+-------------+ +| port_filter | 1 | | ++-------------------------+-------+-------------+ +| i2p_mixed | 2 | | ++-------------------------+-------+-------------+ +| privileged_ports | 3 | | ++-------------------------+-------+-------------+ +| utp_disabled | 4 | | ++-------------------------+-------+-------------+ +| tcp_disabled | 5 | | ++-------------------------+-------+-------------+ +| invalid_local_interface | 6 | | ++-------------------------+-------+-------------+ +| ssrf_mitigation | 7 | | ++-------------------------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +reason + the reason for the peer being blocked. Is one of the values from the + `reason_t`__ enum. + + +__ reference-Alerts.html#reason_t + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_announce_alert +------------------ + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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 ``alert_category::dht`` category. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct dht_announce_alert final : alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::dht; + aux::noexcept_movable
    ip; + int port; + sha1_hash info_hash; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_get_peers_alert +------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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 ``alert_category::dht`` category. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +cache_flushed_alert +------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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 ``alert_category::storage`` 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. + + +__ reference-Alerts.html#alert +__ reference-Torrent_Handle.html#flush_cache() +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Session.html#session + + +.. parsed-literal:: + + + struct cache_flushed_alert final : torrent_alert + { + static constexpr alert_category_t **static_category** = alert_category::storage; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +lsd_peer_alert +-------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct lsd_peer_alert final : peer_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::peer; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +trackerid_alert +--------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +tracker_id() +............ + +.. parsed-literal:: + + char const* **tracker_id** () const; + + +The tracker ID returned by the tracker + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_bootstrap_alert +------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +This `alert`__ is posted when the initial DHT bootstrap is done. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct dht_bootstrap_alert final : alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::dht; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_error_alert +------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +This is posted whenever a torrent is transitioned into the error state. + + + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +filename() +.......... + +.. parsed-literal:: + + char const* **filename** () const; + + +the filename (or object) the error occurred on. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + specifies which error the torrent encountered. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_need_cert_alert +----------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Torrent_Handle.html#set_ssl_certificate() + + +.. parsed-literal:: + + + struct torrent_need_cert_alert final : torrent_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::status; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +incoming_connection_alert +------------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct incoming_connection_alert final : alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::peer; + socket_type_t socket_type; + aux::noexcept_movable endpoint; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +socket_type + tells you what kind of socket the connection was accepted + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +endpoint + is the IP address and port the connection came from. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +add_torrent_alert +----------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +params + This contains copies of the most important fields from the original + `add_torrent_params`__ object, passed to `add_torrent()`__ or + `async_add_torrent()`__. Specifically, these fields are copied: + + * version + * ti + * name + * save_path + * userdata + * tracker_id + * flags + * info_hash + + the info_hash field will be updated with the info-hash of the torrent + specified by ``ti``. + + +__ reference-Add_Torrent.html#add_torrent_params +__ reference-Session.html#add_torrent() +__ reference-Session.html#async_add_torrent() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + set to the error, if one occurred while adding the torrent. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +state_update_alert +------------------ + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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 ``alert_category::status``, but it's not subject to +filtering, since it's only manually posted anyway. + + +__ reference-Alerts.html#alert +__ reference-Session.html#session + + +.. parsed-literal:: + + + struct state_update_alert final : alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::status; + std::vector status; + }; + +.. raw:: html + + + +.. raw:: html + + [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. + + +__ reference-Torrent_Handle.html#torrent_handle + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +session_stats_alert +------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. This `alert`__ does not +have a category, since it's only posted in response to an API call. It +is not subject to the alert_mask filter. + +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 + + +__ reference-Alerts.html#session_stats_alert +__ reference-Session.html#session +__ reference-Session.html#post_session_stats() +__ reference-Session.html#session +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct session_stats_alert final : alert + { + std::string **message** () const override; + span **counters** () const; + + static constexpr alert_category_t **static_category** = {}; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +counters() +.......... + +.. parsed-literal:: + + span **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. + + +__ reference-Stats.html#session_stats_metrics() +__ reference-Session.html#session +__ reference-Stats.html#session_stats_metrics() +__ manual-ref.html#session-statistics + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_error_alert +--------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +posted when something fails in the DHT. This is not necessarily a fatal +error, but it could prevent proper operation + + + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + the error code + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +op + the operation that failed + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_immutable_item_alert +------------------------ + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +target + the target hash of the immutable item. This must + match the SHA-1 hash of the bencoded form of ``item``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +item + the data for this item + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_mutable_item_alert +---------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct dht_mutable_item_alert final : alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::dht; + std::array key; + std::array signature; + std::int64_t seq; + std::string salt; + entry item; + bool authoritative; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +key + the public key that was looked up + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +seq + the sequence number of this item + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +salt + the salt, if any, used to lookup and store this item. If no + salt was used, this is an empty string + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +item + the data for this item + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +authoritative + the last response for mutable data is authoritative. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_put_alert +------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + + + +.. parsed-literal:: + + + 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 public_key; + std::array signature; + std::string salt; + std::int64_t seq; + int num_success; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +target + the target hash the item was stored under if this was an *immutable* + item. + + + +.. raw:: html + + + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +i2p_alert +--------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +this `alert`__ is used to report errors in the i2p SAM connection + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct i2p_alert final : alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::error; + error_code error; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + the error that occurred in the i2p SAM connection + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_outgoing_get_peers_alert +---------------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +This `alert`__ is generated when we send a get_peers request +It belongs to the ``alert_category::dht`` category. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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 endpoint; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +info_hash + the info_hash of the torrent we're looking for peers for. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +obfuscated_info_hash + if this was an obfuscated lookup, this is the info-hash target + actually sent to the node. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +endpoint + the endpoint we're sending this query to + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +log_alert +--------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Session.html#session +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +log_message() +............. + +.. parsed-literal:: + + char const* **log_message** () const; + + +returns the log message + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_log_alert +----------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +log_message() +............. + +.. parsed-literal:: + + char const* **log_message** () const; + + +returns the log message + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +peer_log_alert +-------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +log_message() +............. + +.. parsed-literal:: + + char const* **log_message** () const; + + +returns the log message + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum direction_t +................ + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + ++------------------+-------+-------------+ +| name | value | description | ++==================+=======+=============+ +| incoming_message | 0 | | ++------------------+-------+-------------+ +| outgoing_message | 1 | | ++------------------+-------+-------------+ +| incoming | 2 | | ++------------------+-------+-------------+ +| outgoing | 3 | | ++------------------+-------+-------------+ +| info | 4 | | ++------------------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +event_type + string literal indicating the kind of event. For messages, this is the + message name. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +lsd_error_alert +--------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +posted if the local service discovery socket fails to start properly. +it's categorized as ``alert_category::error``. + + + + +.. parsed-literal:: + + + struct lsd_error_alert final : alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::error; + aux::noexcept_movable
    local_address; + error_code error; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +local_address + the local network the corresponding local service discovery is running + on + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + The error code + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_lookup +---------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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 + + +__ reference-Alerts.html#dht_lookup + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +type + string literal indicating which kind of lookup this is + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +outstanding_requests + the number of outstanding request to individual nodes + this lookup has right now + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +timeouts + the total number of requests that have timed out so far + for this lookup + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +responses + the total number of responses we have received for this + lookup so far for this lookup + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +last_sent + the number of seconds ago the + last message was sent that's still + outstanding + + + +.. raw:: html + + + +.. raw:: html + + [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 + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +target + the node-id or info-hash target for this lookup + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_stats_alert +--------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +contains current DHT state. Posted in response to session::post_dht_stats(). + + + + +.. parsed-literal:: + + + struct dht_stats_alert final : alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = {}; + std::vector active_requests; + std::vector routing_table; + sha1_hash nid; + aux::noexcept_movable local_endpoint; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +active_requests + a vector of the currently running DHT lookups. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +routing_table + contains information about every bucket in the DHT routing + table. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +nid + the node ID of the DHT node instance + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +local_endpoint + the local socket this DHT node is running on + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +incoming_request_alert +---------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +req + the request this peer sent to us + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_log_alert +------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +debug logging of the DHT when alert_category::dht_log is set in the `alert`__ +mask. + + +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +log_message() +............. + +.. parsed-literal:: + + char const* **log_message** () const; + + +the log message + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum dht_module_t +................. + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + ++---------------+-------+-------------+ +| name | value | description | ++===============+=======+=============+ +| tracker | 0 | | ++---------------+-------+-------------+ +| node | 1 | | ++---------------+-------+-------------+ +| routing_table | 2 | | ++---------------+-------+-------------+ +| rpc_manager | 3 | | ++---------------+-------+-------------+ +| traversal | 4 | | ++---------------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +module + the module, or part, of the DHT that produced this log message. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_pkt_alert +------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct dht_pkt_alert final : alert + { + std::string **message** () const override; + span **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 node; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +pkt_buf() +......... + +.. parsed-literal:: + + span **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`__. + + +__ reference-Alerts.html#alert +__ reference-Session.html#pop_alerts() +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum direction_t +................ + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + ++----------+-------+-------------+ +| name | value | description | ++==========+=======+=============+ +| incoming | 0 | | ++----------+-------+-------------+ +| outgoing | 1 | | ++----------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +direction + whether this is an incoming or outgoing packet. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +node + the DHT node we received this packet from, or sent this packet to + (depending on ``direction``). + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_get_peers_reply_alert +------------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +Posted when we receive a response to a DHT get_peers request. + + + + +.. parsed-literal:: + + + struct dht_get_peers_reply_alert final : alert + { + std::string **message** () const override; + int **num_peers** () const; + std::vector **peers** () const; + + static constexpr alert_category_t **static_category** = alert_category::dht_operation; + sha1_hash info_hash; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_direct_response_alert +------------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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`__. + + +__ reference-Alerts.html#response() +__ reference-Bdecoding.html#bdecode_node + + +.. parsed-literal:: + + + struct dht_direct_response_alert final : alert + { + std::string **message** () const override; + bdecode_node **response** () const; + + static constexpr alert_category_t **static_category** = alert_category::dht; + client_data_t userdata; + aux::noexcept_movable endpoint; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +picker_log_alert +---------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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 +alert_category::picker_log). + + + + +.. parsed-literal:: + + + struct picker_log_alert final : peer_alert + { + std::string **message** () const override; + std::vector **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; + }; + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +session_error_alert +------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +this `alert`__ is posted when the `session`__ encounters a serious error, +potentially fatal + + +__ reference-Alerts.html#alert +__ reference-Session.html#session + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + The error code, if one is associated with this error + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_live_nodes_alert +-------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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. + + + + +.. parsed-literal:: + + + struct dht_live_nodes_alert final : alert + { + std::string **message** () const override; + int **num_nodes** () const; + std::vector> **nodes** () const; + + static constexpr alert_category_t **static_category** = alert_category::dht; + sha1_hash node_id; + }; + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +num_nodes() nodes() +................... + +.. parsed-literal:: + + int **num_nodes** () const; + std::vector> **nodes** () const; + + +the number of nodes in the routing table and the actual nodes. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +node_id + the local DHT node's node-ID this routing table belongs to + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +session_stats_header_alert +-------------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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 + + +__ reference-Alerts.html#alert +__ reference-Session.html#post_session_stats() + + +.. parsed-literal:: + + + struct session_stats_header_alert final : alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = {}; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_sample_infohashes_alert +--------------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +posted as a response to a call to session::dht_sample_infohashes() with +the information from the DHT response message. + + + + +.. parsed-literal:: + + + struct dht_sample_infohashes_alert final : alert + { + std::string **message** () const override; + std::vector **samples** () const; + int **num_samples** () const; + int **num_nodes** () const; + std::vector> **nodes** () const; + + static constexpr alert_category_t **static_category** = alert_category::dht_operation; + sha1_hash node_id; + aux::noexcept_movable endpoint; + time_duration const interval; + int const num_infohashes; + }; + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +num_samples() samples() +....................... + +.. parsed-literal:: + + std::vector **samples** () const; + int **num_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()``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +num_nodes() +........... + +.. parsed-literal:: + + int **num_nodes** () const; + + +The total number of nodes returned by ``nodes()``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +nodes() +....... + +.. parsed-literal:: + + std::vector> **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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +node_id + id of the node the request was sent to (and this response was received from) + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +endpoint + the node the request was sent to (and this response was received from) + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +interval + the interval to wait before making another request to this node + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +block_uploaded_alert +-------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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 ``alert_category::upload`` category. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct block_uploaded_alert final : peer_alert + { + std::string **message** () const override; + + int const block_index; + piece_index_t const piece_index; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +alerts_dropped_alert +-------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/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). + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + struct alerts_dropped_alert final : alert + { + std::string **message** () const override; + **static_assert** (num\_alert\_types <= abi\_alert\_count, "need to increase bitset. This is an ABI break"); + + static constexpr alert_category_t **static_category** = alert_category::error; + std::bitset dropped_alerts; + }; + +.. raw:: html + + + +.. raw:: html + + [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. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +socks5_alert +------------ + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +this `alert`__ is posted with SOCKS5 related errors, when a SOCKS5 proxy is +configured. It's enabled with the alert_category::error `alert`__ category. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + + +.. parsed-literal:: + + + 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 ip; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + the error + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +op + the operation that failed + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +ip + the endpoint configured as the proxy + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_prio_alert +--------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +posted when a `prioritize_files()`__ or `file_priority()`__ update of the file +priorities complete, which requires a round-trip to the disk thread. + +If the disk operation fails this `alert`__ won't be posted, but a +`file_error_alert`__ is posted instead, and the torrent is stopped. + + +__ reference-Torrent_Handle.html#prioritize_files() +__ reference-Torrent_Handle.html#file_priority() +__ reference-Alerts.html#alert +__ reference-Alerts.html#file_error_alert + + +.. parsed-literal:: + + + struct file_prio_alert final : torrent_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::storage; + error_code error; + operation_t op; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +error + the error + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +op + the operation that failed + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +oversized_file_alert +-------------------- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +this `alert`__ may be posted when the initial checking of resume data and files +on disk (just existence, not piece hashes) completes. If a file belonging +to the torrent is found on disk, but is larger than the file in the +torrent, that's when this `alert`__ is posted. +the client may want to call `truncate_files()`__ in that case, or perhaps +interpret it as a sign that some other file is in the way, that shouldn't +be overwritten. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Core.html#truncate_files() + + +.. parsed-literal:: + + + struct oversized_file_alert final : torrent_alert + { + std::string **message** () const override; + + static constexpr alert_category_t **static_category** = alert_category::storage; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +alert +----- + +Declared in "`libtorrent/alert.hpp`__" + + +__ include/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()`__) + + +__ reference-Alerts.html#alert +__ reference-Session.html#pop_alerts() + + +.. parsed-literal:: + + + struct 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 **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(); + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +timestamp() +........... + +.. parsed-literal:: + + time_point **timestamp** () const; + + +a timestamp is automatically created in the constructor + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +type() +...... + +.. parsed-literal:: + + 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: + +.. 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... + } + } + } + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +what() +...... + +.. parsed-literal:: + + 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`__. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +message() +......... + +.. parsed-literal:: + + 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. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +category() +.......... + +.. parsed-literal:: + + virtual alert_category_t **category** () const noexcept = 0; + + +returns a bitmask specifying which categories this `alert`__ belong to. + + +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +operation_name() +---------------- + +Declared in "`libtorrent/operations.hpp`__" + + +__ include/libtorrent/operations.hpp + +.. parsed-literal:: + + 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 + + +__ reference-Alerts.html#peer_error_alert +__ reference-Alerts.html#peer_disconnected_alert +__ reference-Alerts.html#operation_t + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +alert_cast() +------------ + +Declared in "`libtorrent/alert.hpp`__" + + +__ include/libtorrent/alert.hpp + +.. parsed-literal:: + + template T const* **alert_cast** (alert const\* a); + template 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 + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum operation_t +---------------- + +Declared in "`libtorrent/operations.hpp`__" + + +__ include/libtorrent/operations.hpp + ++---------------------+-------+-------------------------------------------------------------------------+ +| name | value | description | ++=====================+=======+=========================================================================+ +| unknown | 0 | the error was unexpected and it is unknown which operation caused it | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| bittorrent | 1 | this is used when the bittorrent logic | +| | | determines to disconnect | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| iocontrol | 2 | a call to iocontrol failed | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| getpeername | 3 | a call to ``getpeername()`` failed (querying the remote IP of a | +| | | connection) | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| getname | 4 | a call to getname failed (querying the local IP of a | +| | | connection) | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| alloc_recvbuf | 5 | an attempt to allocate a receive buffer failed | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| alloc_sndbuf | 6 | an attempt to allocate a send buffer failed | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| file_write | 7 | writing to a file failed | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| file_read | 8 | reading from a file failed | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| file | 9 | a non-read and non-write file operation failed | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| sock_write | 10 | a socket write operation failed | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| sock_read | 11 | a socket read operation failed | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| sock_open | 12 | a call to open(), to create a socket socket failed | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| sock_bind | 13 | a call to bind() on a socket failed | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| available | 14 | an attempt to query the number of bytes available to read from a socket | +| | | failed | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| encryption | 15 | a call related to bittorrent protocol encryption failed | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| connect | 16 | an attempt to connect a socket failed | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| ssl_handshake | 17 | establishing an SSL connection failed | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| get_interface | 18 | a connection failed to satisfy the bind interface setting | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| sock_listen | 19 | a call to listen() on a socket | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| sock_bind_to_device | 20 | a call to the ioctl to bind a socket to a specific network device or | +| | | adapter | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| sock_accept | 21 | a call to accept() on a socket | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| parse_address | 22 | convert a string into a valid network address | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| enum_if | 23 | enumeration network devices or adapters | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| file_stat | 24 | invoking stat() on a file | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| file_copy | 25 | copying a file | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| file_fallocate | 26 | allocating storage for a file | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| file_hard_link | 27 | creating a hard link | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| file_remove | 28 | removing a file | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| file_rename | 29 | renaming a file | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| file_open | 30 | opening a file | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| mkdir | 31 | creating a directory | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| check_resume | 32 | check fast resume data against files on disk | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| exception | 33 | an unknown exception | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| alloc_cache_piece | 34 | allocate space for a piece in the cache | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| partfile_move | 35 | move a part-file | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| partfile_read | 36 | read from a part file | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| partfile_write | 37 | write to a part-file | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| hostname_lookup | 38 | a hostname lookup | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| symlink | 39 | create or read a symlink | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| handshake | 40 | handshake with a peer or server | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| sock_option | 41 | set socket option | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| enum_route | 42 | enumeration of network routes | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| file_seek | 43 | moving read/write position in a file, `operation_t::hostname_lookup`__ | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| timer | 44 | an async wait operation on a timer | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| file_mmap | 45 | call to mmap() (or windows counterpart) | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ +| file_truncate | 46 | call to ftruncate() (or SetEndOfFile() on windows) | +| | | | ++---------------------+-------+-------------------------------------------------------------------------+ + + +__ reference-Alerts.html#hostname_lookup + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +int +--- + +Declared in "`libtorrent/alert_types.hpp`__" + + +__ include/libtorrent/alert_types.hpp + +.. raw:: html + + + +user_alert_id + user defined alerts should use IDs greater than this + + + +.. raw:: html + + + +num_alert_types + this constant represents "max_alert_index" + 1 + + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +alert_category_t +---------------- + +Declared in "`libtorrent/alert.hpp`__" + + +__ include/libtorrent/alert.hpp + +.. raw:: html + + + +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 + + + +.. raw:: html + + + +peer + Enables alerts when peers send invalid requests, get banned or + snubbed. + + + +.. raw:: html + + + +port_mapping + Enables alerts for port mapping events. For NAT-PMP and UPnP. + + + +.. raw:: html + + + +storage + Enables alerts for events related to the storage. File errors and + synchronization events for moving the storage, renaming files etc. + + + +.. raw:: html + + + +tracker + Enables all tracker events. Includes announcing to trackers, + receiving responses, warnings and errors. + + + +.. raw:: html + + + +connect + Low level alerts for when peers are connected and disconnected. + + + +.. raw:: html + + + +status + Enables alerts for when a torrent or the `session`__ changes state. + + + __ reference-Session.html#session + +.. raw:: html + + + +ip_block + Alerts when a peer is blocked by the ip blocker or port blocker. + + + +.. raw:: html + + + +performance_warning + Alerts when some limit is reached that might limit the download + or upload rate. + + + +.. raw:: html + + + +dht + Alerts on events in the DHT node. For incoming searches or + bootstrapping being done etc. + + + +.. raw:: html + + + +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`__. + + + __ reference-Stats.html#counters + __ reference-Alerts.html#alert + +.. raw:: html + + + +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. + + + __ reference-Alerts.html#log_alert + __ reference-Session.html#session + +.. raw:: html + + + +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. + + + __ reference-Alerts.html#torrent_log_alert + +.. raw:: html + + + +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. + + + __ reference-Alerts.html#peer_log_alert + +.. raw:: html + + + +incoming_request + enables the `incoming_request_alert`__. + + + __ reference-Alerts.html#incoming_request_alert + +.. raw:: html + + + +dht_log + enables `dht_log_alert`__, debug logging for the DHT + + + __ reference-Alerts.html#dht_log_alert + +.. raw:: html + + + +dht_operation + enable events from pure dht operations not related to torrents + + + +.. raw:: html + + + +port_mapping_log + enables port mapping log events. This log is useful + for debugging the UPnP or NAT-PMP implementation + + + +.. raw:: html + + + +picker_log + enables verbose logging from the piece picker. + + + +.. raw:: html + + + +file_progress + alerts when files complete downloading + + + +.. raw:: html + + + +piece_progress + alerts when pieces complete downloading or fail hash check + + + +.. raw:: html + + + +upload + alerts when we upload blocks to other peers + + + +.. raw:: html + + + +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. + + + +.. raw:: html + + + +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. + + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +stats_metric +------------ + +Declared in "`libtorrent/session_stats.hpp`__" + + +__ include/libtorrent/session_stats.hpp + +describes one statistics metric from the `session`__. For more information, +see the `session statistics`__ section. + + +__ reference-Session.html#session +__ manual-ref.html#session-statistics + + +.. parsed-literal:: + + + struct stats_metric + { + char const* name; + int value_index; + metric_type_t type; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +name + the name of the counter or gauge + + + +.. raw:: html + + + + +.. raw:: html + + [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. + + +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Alerts.html#session_stats_alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +counters +-------- + +Declared in "`libtorrent/performance_counters.hpp`__" + + +__ include/libtorrent/performance_counters.hpp + + + + + +.. parsed-literal:: + + + struct counters + { + **counters** () ; + **counters** (counters const&) ; + counters& **operator=** (counters const&) & ; + std::int64_t **inc_stats_counter** (int c, std\:\:int64\_t value = 1) ; + std::int64_t **operator[]** (int i) const ; + void **set_value** (int c, std\:\:int64\_t value) ; + void **blend_stats_counter** (int c, std\:\:int64\_t value, int ratio) ; + }; + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +inc_stats_counter() operator[]() +................................ + +.. parsed-literal:: + + std::int64_t **inc_stats_counter** (int c, std\:\:int64\_t value = 1) ; + std::int64_t **operator[]** (int i) const ; + + +returns the new value + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +session_stats_metrics() +----------------------- + +Declared in "`libtorrent/session_stats.hpp`__" + + +__ include/libtorrent/session_stats.hpp + +.. parsed-literal:: + + std::vector **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()`__). + + +__ reference-Alerts.html#session_stats_alert +__ reference-Session.html#session +__ reference-Session.html#post_session_stats() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +find_metric_idx() +----------------- + +Declared in "`libtorrent/session_stats.hpp`__" + + +__ include/libtorrent/session_stats.hpp + +.. parsed-literal:: + + 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`__. + + +__ reference-Alerts.html#session_stats_alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum metric_type_t +------------------ + +Declared in "`libtorrent/session_stats.hpp`__" + + +__ include/libtorrent/session_stats.hpp + ++---------+-------+-------------+ +| name | value | description | ++=========+=======+=============+ +| counter | 0 | | ++---------+-------+-------------+ +| gauge | 1 | | ++---------+-------+-------------+ + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +ip_filter +--------- + +Declared in "`libtorrent/ip_filter.hpp`__" + + +__ include/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. + + +__ reference-Filter.html#ip_filter + + +.. parsed-literal:: + + + struct ip_filter + { + **ip_filter** (); + **~ip_filter** (); + **ip_filter** (ip\_filter&&); + ip_filter& **operator=** (ip\_filter const&); + **ip_filter** (ip\_filter const&); + ip_filter& **operator=** (ip\_filter&&); + bool **empty** () const; + 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, + }; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +empty() +....... + +.. parsed-literal:: + + bool **empty** () const; + + +returns true if the filter does not contain any rules + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +add_rule() +.......... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +access() +........ + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +export_filter() +............... + +.. parsed-literal:: + + 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. + + +__ reference-Bencoding.html#entry + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum access_flags +................. + +Declared in "`libtorrent/ip_filter.hpp`__" + + +__ include/libtorrent/ip_filter.hpp + ++---------+-------+----------------------------------------------------------+ +| name | value | description | ++=========+=======+==========================================================+ +| blocked | 1 | indicates that IPs in this range should not be connected | +| | | to nor accepted as incoming connections | +| | | | ++---------+-------+----------------------------------------------------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +port_filter +----------- + +Declared in "`libtorrent/ip_filter.hpp`__" + + +__ include/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. + + + + +.. parsed-literal:: + + + class port_filter + { + **port_filter** (); + port_filter& **operator=** (port\_filter const&); + **port_filter** (port\_filter const&); + port_filter& **operator=** (port\_filter&&); + **port_filter** (port\_filter&&); + **~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, + }; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +add_rule() +.......... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +access() +........ + +.. parsed-literal:: + + 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`__. + + +__ reference-Filter.html#access_flags + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum access_flags +................. + +Declared in "`libtorrent/ip_filter.hpp`__" + + +__ include/libtorrent/ip_filter.hpp + ++---------+-------+---------------------------------------------------+ +| name | value | description | ++=========+=======+===================================================+ +| blocked | 1 | this flag indicates that destination ports in the | +| | | range should not be connected to | +| | | | ++---------+-------+---------------------------------------------------+ + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +storage_params +-------------- + +Declared in "`libtorrent/storage_defs.hpp`__" + + +__ include/libtorrent/storage_defs.hpp + +a parameter pack used to construct the storage for a torrent, used in +`disk_interface`__ + + +__ reference-Custom_Storage.html#disk_interface + + +.. parsed-literal:: + + + struct 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); + + file_storage const& files; + file_storage const* **mapped_files** = nullptr; + std::string const& path; + storage_mode_t **mode** {storage_mode_sparse}; + aux::vector const& priorities; + sha1_hash info_hash; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_slice +---------- + +Declared in "`libtorrent/file_storage.hpp`__" + + +__ include/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. + + +__ reference-Torrent_Info.html#torrent_info + + +.. parsed-literal:: + + + struct file_slice + { + file_index_t file_index; + std::int64_t offset; + std::int64_t size; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +file_index + the index of the file + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +offset + the offset from the start of the file, in bytes + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +size + the size of the window, in bytes + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_storage +------------ + +Declared in "`libtorrent/file_storage.hpp`__" + + +__ include/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. + + + + +.. parsed-literal:: + + + class file_storage + { + bool **is_valid** () const; + void **reserve** (int num\_files); + void **add_file_borrow** (error\_code& ec, 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() + , char const\* root\_hash = nullptr); + 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() + , char const\* root\_hash = nullptr); + void **add_file** (error\_code& ec, 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() + , char const\* root\_hash = nullptr); + 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() + , char const\* root\_hash = nullptr); + void **rename_file** (file\_index\_t index, std\:\:string const& new\_filename); + std::vector **map_block** (piece\_index\_t piece, std\:\:int64\_t offset + , std\:\:int64\_t 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_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_range** () const noexcept; + void **set_piece_length** (int l); + int **piece_length** () const; + int **piece_size** (piece\_index\_t index) const; + int **piece_size2** (piece\_index\_t index) const; + int **blocks_in_piece2** (piece\_index\_t index) const; + void **set_name** (std\:\:string const& n); + std::string const& **name** () const; + void **swap** (file\_storage& ti) noexcept; + void **canonicalize** (); + std::time_t **mtime** (file\_index\_t index) const; + sha256_hash **root** (file\_index\_t index) const; + char const* **root_ptr** (file\_index\_t const index) const; + bool **pad_file_at** (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::int64_t **file_size** (file\_index\_t index) const; + std::string **symlink** (file\_index\_t index) const; + std::int64_t **file_offset** (file\_index\_t index) const; + int **file_num_blocks** (file\_index\_t index) const; + int **file_num_pieces** (file\_index\_t index) const; + index_range **file_piece_range** (file\_index\_t) const; + int **file_first_piece_node** (file\_index\_t index) const; + int **file_first_block_node** (file\_index\_t index) const; + std::uint32_t **file_path_hash** (file\_index\_t index, std\:\:string const& save\_path) const; + void **all_path_hashes** (std\:\:unordered\_set& table) 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_piece** (piece\_index\_t piece) const; + file_index_t **file_index_at_offset** (std\:\:int64\_t offset) const; + file_index_t **file_index_for_root** (sha256\_hash const& root\_hash) const; + piece_index_t **piece_index_at_file** (file\_index\_t f) const; + void **sanitize_symlinks** (); + bool **v2** () const; + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +is_valid() +.......... + +.. parsed-literal:: + + 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. + + +__ reference-Storage.html#file_storage +__ reference-Storage.html#file_storage + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +reserve() +......... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +add_file() add_file_borrow() +............................ + +.. parsed-literal:: + + void **add_file_borrow** (error\_code& ec, 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() + , char const\* root\_hash = nullptr); + 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() + , char const\* root\_hash = nullptr); + void **add_file** (error\_code& ec, 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() + , char const\* root\_hash = nullptr); + 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() + , char const\* root\_hash = nullptr); + + +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. + +``root_hash`` is an optional pointer to a 32 byte SHA-256 hash, being +the merkle tree root hash for this file. This is only used for v2 +torrents. If the ``root hash`` is specified for one file, it has to +be specified for all, otherwise this function will fail. +Note that the buffer ``root_hash`` points to must out-live the +`file_storage`__ object, it will not be copied. This parameter is only +used when *loading* torrents, that already have their file hashes +computed. When creating torrents, the file hashes will be computed by +the piece hashes. + +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``. + +The overloads that take an `error_code` reference will report failures +via that variable, otherwise `system_error` is thrown. + + +__ reference-Storage.html#file_storage +__ reference-Storage.html#flag_symlink +__ reference-Storage.html#file_storage + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +rename_file() +............. + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +map_block() +........... + +.. parsed-literal:: + + std::vector **map_block** (piece\_index\_t piece, std\:\:int64\_t offset + , std\:\:int64\_t 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. + + +__ reference-Storage.html#file_slice +__ reference-Torrent_Info.html#map_file() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +map_file() +.......... + +.. parsed-literal:: + + 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. + + +__ reference-Core.html#peer_request +__ reference-Torrent_Info.html#map_block() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +num_files() +........... + +.. parsed-literal:: + + int **num_files** () const noexcept; + + +returns the number of files in the `file_storage`__ + + +__ reference-Storage.html#file_storage + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +end_file() +.......... + +.. parsed-literal:: + + file_index_t **end_file** () const noexcept; + + +returns the index of the one-past-end file in the file storage + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_range() +............ + +.. parsed-literal:: + + index_range **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`__. + + +__ reference-Storage.html#file_storage + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +total_size() +............ + +.. parsed-literal:: + + std::int64_t **total_size** () const; + + +returns the total number of bytes all the files in this torrent spans + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +num_pieces() set_num_pieces() +............................. + +.. parsed-literal:: + + void **set_num_pieces** (int n); + int **num_pieces** () const; + + +set and get the number of pieces in the torrent + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +end_piece() +........... + +.. parsed-literal:: + + piece_index_t **end_piece** () const; + + +returns the index of the one-past-end piece in the file storage + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +last_piece() +............ + +.. parsed-literal:: + + 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). + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +piece_range() +............. + +.. parsed-literal:: + + index_range **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`__. + + +__ reference-Storage.html#file_storage + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +set_piece_length() piece_length() +................................. + +.. parsed-literal:: + + void **set_piece_length** (int l); + int **piece_length** () const; + + +set and get the size of each piece in this torrent. It must be a power of two +and at least 16 kiB. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +piece_size() +............ + +.. parsed-literal:: + + 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. + + +__ reference-Torrent_Info.html#piece_length() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +piece_size2() +............. + +.. parsed-literal:: + + int **piece_size2** (piece\_index\_t index) const; + + +Returns the size of the given piece. If the piece spans multiple files, +only the first file is considered part of the piece. This is used for +v2 torrents, where all files are piece aligned and padded. i.e. The pad +files are not considered part of the piece for this purpose. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +blocks_in_piece2() +.................. + +.. parsed-literal:: + + int **blocks_in_piece2** (piece\_index\_t index) const; + + +returns the number of blocks in the specified piece, for v2 torrents. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +name() set_name() +................. + +.. parsed-literal:: + + void **set_name** (std\:\:string const& n); + std::string const& **name** () 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +swap() +...... + +.. parsed-literal:: + + void **swap** (file\_storage& ti) noexcept; + + +swap all content of *this* with *ti*. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +canonicalize() +.............. + +.. parsed-literal:: + + void **canonicalize** (); + + +arrange files and padding to match the canonical form required +by BEP 52 + + + +.. raw:: html + + + + + + + + + + + + +.. raw:: html + + [report issue] + + + +root() file_name() mtime() symlink() file_size() hash() file_offset() root_ptr() pad_file_at() file_path() +.......................................................................................................... + +.. parsed-literal:: + + std::time_t **mtime** (file\_index\_t index) const; + sha256_hash **root** (file\_index\_t index) const; + char const* **root_ptr** (file\_index\_t const index) const; + bool **pad_file_at** (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::int64_t **file_size** (file\_index\_t index) const; + std::string **symlink** (file\_index\_t index) const; + std::int64_t **file_offset** (file\_index\_t index) const; + + +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. + +``root()`` returns the SHA-256 merkle tree root of the specified file, +in case this is a v2 torrent. Otherwise returns zeros. +``root_ptr()`` returns a pointer to the SHA-256 merkle tree root hash +for the specified file. The pointer points into storage referred to +when the file was added, it is not owned by this object. Torrents +that are not v2 torrents return nullptr. + +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). + + + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +file_piece_range() file_num_blocks() file_num_pieces() +...................................................... + +.. parsed-literal:: + + int **file_num_blocks** (file\_index\_t index) const; + int **file_num_pieces** (file\_index\_t index) const; + index_range **file_piece_range** (file\_index\_t) const; + + +Returns the number of pieces or blocks the file at `index` spans, +under the assumption that the file is aligned to the start of a piece. +This is only meaningful for v2 torrents, where files are guaranteed +such alignment. +These numbers are used to size and navigate the merkle hash tree for +each file. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +file_first_block_node() file_first_piece_node() +............................................... + +.. parsed-literal:: + + int **file_first_piece_node** (file\_index\_t index) const; + int **file_first_block_node** (file\_index\_t index) const; + + +index of first piece node in the merkle tree + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_path_hash() +................ + +.. parsed-literal:: + + std::uint32_t **file_path_hash** (file\_index\_t index, std\:\:string const& save\_path) const; + + +returns the crc32 hash of file_path(index) + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +all_path_hashes() +................. + +.. parsed-literal:: + + void **all_path_hashes** (std\:\:unordered\_set& 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_flags() +............ + +.. parsed-literal:: + + 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``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_absolute_path() +.................... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +file_index_at_offset() file_index_at_piece() +............................................ + +.. parsed-literal:: + + file_index_t **file_index_at_piece** (piece\_index\_t piece) const; + 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 + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_index_for_root() +..................... + +.. parsed-literal:: + + file_index_t **file_index_for_root** (sha256\_hash const& root\_hash) const; + + +finds the file with the given root hash and returns its index +if there is no file with the root hash, file_index_t{-1} is returned + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +piece_index_at_file() +..................... + +.. parsed-literal:: + + piece_index_t **piece_index_at_file** (file\_index\_t f) const; + + +returns the piece index the given file starts at + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +sanitize_symlinks() +................... + +.. parsed-literal:: + + void **sanitize_symlinks** (); + + +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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +v2() +.... + +.. parsed-literal:: + + bool **v2** () const; + + +returns true if this torrent contains v2 metadata. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +flag_pad_file + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +flag_hidden + this file has the hidden attribute set. This is primarily + a windows attribute + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +flag_executable + this file has the executable attribute set. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +flag_symlink + this file is a symbolic link. It should have a link + target string associated with it. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +default_disk_io_constructor() +----------------------------- + +Declared in "`libtorrent/session.hpp`__" + + +__ include/libtorrent/session.hpp + +.. parsed-literal:: + + std::unique_ptr **default_disk_io_constructor** ( + io\_context& ios, settings\_interface const&, counters& cnt); + + +the constructor function for the default storage. On systems that support +memory mapped files (and a 64 bit address space) the memory mapped storage +will be constructed, otherwise the portable posix storage. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +posix_disk_io_constructor() +--------------------------- + +Declared in "`libtorrent/posix_disk_io.hpp`__" + + +__ include/libtorrent/posix_disk_io.hpp + +.. parsed-literal:: + + std::unique_ptr **posix_disk_io_constructor** ( + io\_context& ios, settings\_interface const&, counters& cnt); + + +this is a simple posix disk I/O back-end, used for systems that don't +have a 64 bit virtual address space or don't support memory mapped files. +It's implemented using portable C file functions and is single-threaded. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +mmap_disk_io_constructor() +-------------------------- + +Declared in "`libtorrent/mmap_disk_io.hpp`__" + + +__ include/libtorrent/mmap_disk_io.hpp + +.. parsed-literal:: + + std::unique_ptr **mmap_disk_io_constructor** ( + io\_context& ios, settings\_interface const&, counters& cnt); + + +constructs a memory mapped file disk I/O object. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +disabled_disk_io_constructor() +------------------------------ + +Declared in "`libtorrent/disabled_disk_io.hpp`__" + + +__ include/libtorrent/disabled_disk_io.hpp + +.. parsed-literal:: + + std::unique_ptr **disabled_disk_io_constructor** ( + io\_context& ios, settings\_interface const&, counters& cnt); + + +creates a disk io object that discards all data written to it, and only +returns zero-buffers when read from. May be useful for testing and +benchmarking. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum storage_mode_t +------------------- + +Declared in "`libtorrent/storage_defs.hpp`__" + + +__ include/libtorrent/storage_defs.hpp + ++-----------------------+-------+----------------------------------------------------------------------------+ +| name | value | description | ++=======================+=======+============================================================================+ +| storage_mode_allocate | 0 | All pieces will be written to their final position, all files will be | +| | | allocated in full when the torrent is first started. This mode minimizes | +| | | fragmentation but could be a costly operation. | +| | | | ++-----------------------+-------+----------------------------------------------------------------------------+ +| storage_mode_sparse | 1 | All pieces will be written to the place where they belong and sparse files | +| | | will be used. This is the recommended, and default mode. | +| | | | ++-----------------------+-------+----------------------------------------------------------------------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum status_t +------------- + +Declared in "`libtorrent/storage_defs.hpp`__" + + +__ include/libtorrent/storage_defs.hpp + ++------------------+-------+------------------------------------------------------------------------+ +| name | value | description | ++==================+=======+========================================================================+ +| no_error | 0 | | ++------------------+-------+------------------------------------------------------------------------+ +| fatal_disk_error | 1 | | ++------------------+-------+------------------------------------------------------------------------+ +| need_full_check | 2 | | ++------------------+-------+------------------------------------------------------------------------+ +| file_exist | 3 | | ++------------------+-------+------------------------------------------------------------------------+ +| oversized_file | 16 | this is not an enum value, but a flag that can be set in the return | +| | | from async_check_files, in case an existing file was found larger than | +| | | specified in the torrent. i.e. it has garbage at the end | +| | | the `status_t`__ field is used for this to preserve ABI. | +| | | | ++------------------+-------+------------------------------------------------------------------------+ + + +__ reference-Storage.html#status_t + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum move_flags_t +----------------- + +Declared in "`libtorrent/storage_defs.hpp`__" + + +__ include/libtorrent/storage_defs.hpp + ++----------------------+-------+------------------------------------------------------------+ +| name | value | description | ++======================+=======+============================================================+ +| always_replace_files | 0 | replace any files in the destination when copying | +| | | or moving the storage | +| | | | ++----------------------+-------+------------------------------------------------------------+ +| fail_if_exist | 1 | 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. | +| | | | ++----------------------+-------+------------------------------------------------------------+ +| dont_replace | 2 | if any file exist in the target, take those files instead | +| | | of the ones we may have in the source. | +| | | | ++----------------------+-------+------------------------------------------------------------+ + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking + +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 ```` 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&, client_data_t); + +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 + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +peer_connection_handle +---------------------- + +Declared in "`libtorrent/peer_connection_handle.hpp`__" + + +__ reference-Plugins.html#plugin +__ reference-Plugins.html#plugin +__ reference-Session.html#session +__ reference-Plugins.html#plugin +__ reference-Plugins.html#plugin +__ reference-Plugins.html#plugin +__ reference-Plugins.html#plugin +__ reference-Plugins.html#plugin +__ reference-Plugins.html#plugin +__ reference-Plugins.html#plugin +__ reference-Plugins.html#plugin +__ reference-Plugins.html#torrent_plugin +__ reference-Plugins.html#peer_plugin +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert +__ include/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 + + +__ reference-Plugins.html#peer_connection_handle + + +.. parsed-literal:: + + + struct peer_connection_handle + { + explicit **peer_connection_handle** (std\:\:weak\_ptr impl); + connection_type **type** () const; + peer_plugin const* **find_plugin** (string\_view type) const; + void **add_extension** (std\:\:shared\_ptr); + bool **is_seed** () const; + bool **upload_only** () const; + peer_id const& **pid** () const; + bool **has_piece** (piece\_index\_t i) const; + bool **is_choked** () const; + bool **is_interesting** () const; + bool **has_peer_choked** () const; + bool **is_peer_interested** () const; + void **maybe_unchoke_this_peer** (); + void **choke_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_disconnecting** () 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 **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 **native_handle** () const; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +bt_peer_connection_handle +------------------------- + +Declared in "`libtorrent/peer_connection_handle.hpp`__" + + +__ include/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. + + +__ reference-Plugins.html#bt_peer_connection_handle + + +.. parsed-literal:: + + + 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); + void **switch_send_crypto** (std\:\:shared\_ptr crypto); + std::shared_ptr **native_handle** () const; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +plugin +------ + +Declared in "`libtorrent/extensions.hpp`__" + + +__ include/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. + + +__ reference-Session.html#session +__ reference-Plugins.html#plugin +__ reference-Session.html#session + + +.. parsed-literal:: + + + struct plugin + { + virtual feature_flags_t **implemented_features** (); + virtual std::shared_ptr **new_torrent** (torrent\_handle const&, client\_data\_t); + virtual void **added** (session\_handle const&); + virtual void **abort** (); + 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** (info\_hash\_t 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 std::map **save_state** () const; + virtual void **load_state** (std\:\:map 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +implemented_features() +...................... + +.. parsed-literal:: + + 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. + + +__ reference-Plugins.html#plugin + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +new_torrent() +............. + +.. parsed-literal:: + + virtual std::shared_ptr **new_torrent** (torrent\_handle const&, client\_data\_t); + + +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 `client_data_t`__ 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). + + +__ reference-Session.html#session +__ reference-Add_Torrent.html#client_data_t +__ reference-Add_Torrent.html#add_torrent_params +__ reference-Plugins.html#plugin +__ reference-Plugins.html#torrent_plugin +__ reference-Plugins.html#torrent_plugin + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +added() +....... + +.. parsed-literal:: + + virtual void **added** (session\_handle const&); + + +called when `plugin`__ is added to a `session`__ + + +__ reference-Plugins.html#plugin +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +abort() +....... + +.. parsed-literal:: + + virtual void **abort** (); + + +called when the `session`__ is aborted +the `plugin`__ should perform any cleanup necessary to allow the session's +destruction (e.g. cancel outstanding async operations) + + +__ reference-Session.html#session +__ reference-Plugins.html#plugin + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +on_dht_request() +................ + +.. parsed-literal:: + + 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()`__. + + +__ reference-Plugins.html#plugin +__ reference-Plugins.html#implemented_features() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +on_alert() +.......... + +.. parsed-literal:: + + 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()`__. + + +__ reference-Alerts.html#alert +__ reference-Plugins.html#plugin +__ reference-Plugins.html#implemented_features() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +on_unknown_torrent() +.................... + +.. parsed-literal:: + + virtual bool **on_unknown_torrent** (info\_hash\_t const& */\* info\_hash \*/* + , peer\_connection\_handle const& */\* pc \*/*, add\_torrent\_params& */\* p \*/*); + + +return true if the `add_torrent_params`__ should be added + + +__ reference-Add_Torrent.html#add_torrent_params + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +on_tick() +......... + +.. parsed-literal:: + + 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()`__. + + +__ reference-Plugins.html#plugin +__ reference-Plugins.html#implemented_features() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +get_unchoke_priority() +...................... + +.. parsed-literal:: + + 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. + + +__ reference-Plugins.html#plugin +__ reference-Plugins.html#plugin +__ reference-Plugins.html#implemented_features() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +load_state() +............ + +.. parsed-literal:: + + virtual void **load_state** (std\:\:map const&); + + +called on startup while loading settings state from the `session_params`__ + + +__ reference-Session.html#session_params + +.. raw:: html + + + +.. raw:: html + + [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. + + +__ reference-Plugins.html#plugin + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +tick_feature + include this bit if your `plugin`__ needs to have `on_tick()`__ called + + +__ reference-Plugins.html#plugin +__ reference-Plugins.html#on_tick() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +dht_request_feature + include this bit if your `plugin`__ needs to have `on_dht_request()`__ + called + + +__ reference-Plugins.html#plugin +__ reference-Plugins.html#on_dht_request() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +alert_feature + include this bit if your `plugin`__ needs to have `on_alert()`__ + called + + +__ reference-Plugins.html#plugin +__ reference-Plugins.html#on_alert() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_plugin +-------------- + +Declared in "`libtorrent/extensions.hpp`__" + + +__ include/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. + + + + +.. parsed-literal:: + + + struct torrent_plugin + { + virtual std::shared_ptr **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_resume** (); + virtual bool **on_pause** (); + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +new_connection() +................ + +.. parsed-literal:: + + virtual std::shared_ptr **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. + + +__ reference-Plugins.html#peer_plugin +__ reference-Plugins.html#peer_plugin + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +on_piece_failed() on_piece_pass() +................................. + +.. parsed-literal:: + + 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``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +tick() +...... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +on_pause() on_resume() +...................... + +.. parsed-literal:: + + virtual bool **on_resume** (); + virtual bool **on_pause** (); + + +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. + + +__ reference-Plugins.html#plugin + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +on_files_checked() +.................. + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +on_state() +.......... + +.. parsed-literal:: + + 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 + + +__ reference-Torrent_Status.html#state_t + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +on_add_peer() +............. + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +first_time + this is the first time we see this peer + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +filtered + this peer was not added because it was + filtered by the IP filter + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +peer_plugin +----------- + +Declared in "`libtorrent/extensions.hpp`__" + + +__ include/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 + + +__ reference-Plugins.html#plugin + + +.. parsed-literal:: + + + 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); + virtual bool **on_extension_handshake** (bdecode\_node const&); + virtual bool **on_bitfield** (bitfield const& */\*bitfield\*/*); + virtual bool **on_not_interested** (); + virtual bool **on_request** (peer\_request const&); + virtual bool **on_choke** (); + virtual bool **on_have** (piece\_index\_t); + virtual bool **on_dont_have** (piece\_index\_t); + virtual bool **on_unchoke** (); + virtual bool **on_interested** (); + virtual bool **on_have_none** (); + virtual bool **on_have_all** (); + virtual bool **on_allowed_fast** (piece\_index\_t); + virtual bool **on_piece** (peer\_request const& */\*piece\*/* + , span */\*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_suggest** (piece\_index\_t); + virtual void **sent_reject_request** (peer\_request const&); + virtual void **sent_request** (peer\_request const&); + virtual void **sent_choke** (); + virtual void **sent_allow_fast** (piece\_index\_t); + virtual void **sent_cancel** (peer\_request const&); + virtual void **sent_have_none** (); + virtual void **sent_have_all** (); + virtual void **sent_have** (piece\_index\_t); + virtual void **sent_unchoke** (); + virtual void **sent_piece** (peer\_request const&); + virtual void **sent_interested** (); + virtual void **sent_not_interested** (); + virtual void **sent_payload** (int */\* bytes \*/*); + virtual bool **can_disconnect** (error\_code const& */\*ec\*/*); + virtual bool **on_extended** (int */\*length\*/*, int */\*msg\*/*, + span */\*body\*/*); + virtual bool **on_unknown_message** (int */\*length\*/*, int */\*msg\*/*, + span */\*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&); + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +type() +...... + +.. parsed-literal:: + + virtual string_view **type** () const; + + +This function is expected to return the name of +the `plugin`__. + + +__ reference-Plugins.html#plugin + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +add_handshake() +............... + +.. parsed-literal:: + + virtual void **add_handshake** (entry&); + + +can add entries to the extension handshake +this is not called for web seeds + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +on_disconnect() +............... + +.. parsed-literal:: + + virtual void **on_disconnect** (error\_code const&); + + +called when the peer is being disconnected. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +on_connected() +.............. + +.. parsed-literal:: + + 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. + + +__ reference-Plugins.html#plugin + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +on_handshake() +.............. + +.. parsed-literal:: + + virtual bool **on_handshake** (span); + + +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 + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +on_extension_handshake() +........................ + +.. parsed-literal:: + + 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 + + +__ reference-Plugins.html#peer_plugin + +.. raw:: html + + + + + + + + + + + + + +.. raw:: html + + [report issue] + + + +on_allowed_fast() on_choke() on_interested() on_bitfield() on_have() on_request() on_have_all() on_have_none() on_unchoke() on_dont_have() on_not_interested() +.............................................................................................................................................................. + +.. parsed-literal:: + + virtual bool **on_bitfield** (bitfield const& */\*bitfield\*/*); + virtual bool **on_not_interested** (); + virtual bool **on_request** (peer\_request const&); + virtual bool **on_choke** (); + virtual bool **on_have** (piece\_index\_t); + virtual bool **on_dont_have** (piece\_index\_t); + virtual bool **on_unchoke** (); + virtual bool **on_interested** (); + virtual bool **on_have_none** (); + virtual bool **on_have_all** (); + virtual bool **on_allowed_fast** (piece\_index\_t); + + +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. + + +__ reference-Plugins.html#plugin +__ reference-Plugins.html#plugin + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +on_piece() +.......... + +.. parsed-literal:: + + virtual bool **on_piece** (peer\_request const& */\*piece\*/* + , span */\*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. + + + +.. raw:: html + + + + + + + +.. raw:: html + + [report issue] + + + +sent_not_interested() sent_piece() sent_unchoke() sent_interested() sent_have() +............................................................................... + +.. parsed-literal:: + + virtual void **sent_have** (piece\_index\_t); + virtual void **sent_unchoke** (); + virtual void **sent_piece** (peer\_request const&); + virtual void **sent_interested** (); + virtual void **sent_not_interested** (); + + +called after a choke message has been sent to the peer + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +sent_payload() +.............. + +.. parsed-literal:: + + virtual void **sent_payload** (int */\* bytes \*/*); + + +called after piece data has been sent to the peer +this can be used for stats book keeping + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +can_disconnect() +................ + +.. parsed-literal:: + + 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. + + +__ reference-Plugins.html#plugin + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +on_extended() +............. + +.. parsed-literal:: + + virtual bool **on_extended** (int */\*length\*/*, int */\*msg\*/*, + span */\*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. + + +__ reference-Plugins.html#plugin +__ reference-Plugins.html#plugin + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +on_unknown_message() +.................... + +.. parsed-literal:: + + virtual bool **on_unknown_message** (int */\*length\*/*, int */\*msg\*/*, + span */\*body\*/*); + + +this is not called for web seeds + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +on_piece_failed() on_piece_pass() +................................. + +.. parsed-literal:: + + 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 + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +tick() +...... + +.. parsed-literal:: + + virtual void **tick** (); + + +called approximately once every second + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +write_request() +............... + +.. parsed-literal:: + + 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. + + +__ reference-Plugins.html#plugin + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +crypto_plugin +------------- + +Declared in "`libtorrent/extensions.hpp`__" + + +__ include/libtorrent/extensions.hpp + + + + + +.. parsed-literal:: + + + struct crypto_plugin + { + virtual void **set_outgoing_key** (span key) = 0; + virtual void **set_incoming_key** (span key) = 0; + **encrypt** (span> */\*send\_vec\*/*) = 0; + virtual std::tuple **decrypt** (span> */\*receive\_vec\*/*) = 0; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +decrypt() +......... + +.. parsed-literal:: + + virtual std::tuple **decrypt** (span> */\*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 + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +create_ut_metadata_plugin() +--------------------------- + +Declared in "`libtorrent/extensions/ut_metadata.hpp`__" + + +__ include/libtorrent/extensions/ut_metadata.hpp + +.. parsed-literal:: + + std::shared_ptr **create_ut_metadata_plugin** (torrent\_handle const&, client\_data\_t); + + +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()`__. + + +__ reference-Session.html#session +__ reference-Torrent_Handle.html#add_extension() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +create_smart_ban_plugin() +------------------------- + +Declared in "`libtorrent/extensions/smart_ban.hpp`__" + + +__ include/libtorrent/extensions/smart_ban.hpp + +.. parsed-literal:: + + std::shared_ptr **create_smart_ban_plugin** (torrent\_handle const&, client\_data\_t); + + +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()`__. + + +__ reference-Torrent_Handle.html#add_extension() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +create_ut_pex_plugin() +---------------------- + +Declared in "`libtorrent/extensions/ut_pex.hpp`__" + + +__ include/libtorrent/extensions/ut_pex.hpp + +.. parsed-literal:: + + std::shared_ptr **create_ut_pex_plugin** (torrent\_handle const&, client\_data\_t); + + +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()`__. + + +__ reference-Session.html#session +__ reference-Torrent_Handle.html#add_extension() + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +block_info +---------- + +Declared in "`libtorrent/torrent_handle.hpp`__" + + +__ include/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. + + + + +.. parsed-literal:: + + + struct block_info + { + tcp::endpoint **peer** () const; + void **set_peer** (tcp\:\:endpoint const& ep); + + enum block_state_t + { + none, + requested, + writing, + finished, + }; + + unsigned bytes_progress:15; + unsigned block_size:15; + unsigned state:2; + unsigned num_peers:14; + }; + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +set_peer() peer() +................. + +.. parsed-literal:: + + tcp::endpoint **peer** () const; + void **set_peer** (tcp\:\:endpoint const& ep); + + +The peer is the ip address of the peer this block was downloaded from. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum block_state_t +.................. + +Declared in "`libtorrent/torrent_handle.hpp`__" + + +__ include/libtorrent/torrent_handle.hpp + ++-----------+-------+------------------------------------------------------------------+ +| name | value | description | ++===========+=======+==================================================================+ +| none | 0 | This block has not been downloaded or requested form any peer. | +| | | | ++-----------+-------+------------------------------------------------------------------+ +| requested | 1 | The block has been requested, but not completely downloaded yet. | +| | | | ++-----------+-------+------------------------------------------------------------------+ +| writing | 2 | The block has been downloaded and is currently queued for being | +| | | written to disk. | +| | | | ++-----------+-------+------------------------------------------------------------------+ +| finished | 3 | The block has been written to disk. | +| | | | ++-----------+-------+------------------------------------------------------------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +bytes_progress + the number of bytes that have been received for this block + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +block_size + the total number of bytes in this block. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +state + the state this block is in (see `block_state_t`__) + + +__ reference-Torrent_Handle.html#block_state_t + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +partial_piece_info +------------------ + +Declared in "`libtorrent/torrent_handle.hpp`__" + + +__ include/libtorrent/torrent_handle.hpp + +This class holds information about pieces that have outstanding requests +or outstanding writes + + + + +.. parsed-literal:: + + + struct partial_piece_info + { + piece_index_t piece_index; + int blocks_in_piece; + int finished; + int writing; + int requested; + block_info const* blocks; + }; + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +blocks_in_piece + the number of blocks in this piece + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +finished + the number of blocks that are in the finished state + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +writing + the number of blocks that are in the writing state + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +requested + the number of blocks that are in the requested state + + + +.. raw:: html + + + +.. raw:: html + + [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. + + +__ reference-Session.html#session +__ reference-Torrent_Handle.html#get_download_queue() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_handle +-------------- + +Declared in "`libtorrent/torrent_handle.hpp`__" + + +__ include/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. + + + +__ reference-Torrent_Handle.html#status() +__ reference-Torrent_Handle.html#torrent_handle +__ reference-Torrent_Info.html#is_valid() + + +.. parsed-literal:: + + + struct torrent_handle + { + friend std::size_t **hash_value** (torrent\_handle const& th); + **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& v) const; + torrent_status **status** (status\_flags\_t flags = status\_flags\_t\:\:all()) const; + void **get_download_queue** (std\:\:vector& queue) const; + std::vector **get_download_queue** () const; + void **reset_piece_deadline** (piece\_index\_t index) const; + void **clear_piece_deadlines** () const; + void **set_piece_deadline** (piece\_index\_t index, int deadline, deadline\_flags\_t flags = {}) const; + void **file_progress** (std\:\:vector& progress, file\_progress\_flags\_t flags = {}) const; + std::vector **file_progress** (file\_progress\_flags\_t flags = {}) const; + std::vector **file_status** () const; + void **clear_error** () const; + void **add_tracker** (announce\_entry const&) const; + void **replace_trackers** (std\:\:vector const&) const; + std::vector **trackers** () const; + void **remove_url_seed** (std\:\:string const& url) const; + void **add_url_seed** (std\:\:string const& url) const; + std::set **url_seeds** () const; + std::set **http_seeds** () const; + void **add_http_seed** (std\:\:string const& url) const; + void **remove_http_seed** (std\:\:string const& url) const; + void **add_extension** ( + std\:\:function(torrent\_handle const&, client\_data\_t)> const& ext + , client\_data\_t userdata = client\_data\_t{}); + bool **set_metadata** (span metadata) const; + bool **is_valid** () const; + void **resume** () const; + void **pause** (pause\_flags\_t flags = {}) const; + torrent_flags_t **flags** () const; + void **set_flags** (torrent\_flags\_t flags, torrent\_flags\_t mask) const; + void **unset_flags** (torrent\_flags\_t flags) const; + void **set_flags** (torrent\_flags\_t flags) 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; + void **queue_position_down** () const; + void **queue_position_up** () const; + void **queue_position_top** () const; + void **queue_position_bottom** () const; + queue_position_t **queue_position** () const; + void **queue_position_set** (queue\_position\_t p) const; + void **set_ssl_certificate_buffer** (std\:\:string const& certificate + , std\:\:string const& private\_key + , std\:\:string const& dh\_params); + void **set_ssl_certificate** (std\:\:string const& certificate + , std\:\:string const& private\_key + , std\:\:string const& dh\_params + , std\:\:string const& passphrase = ""); + std::shared_ptr **torrent_file_with_hashes** () const; + std::shared_ptr **torrent_file** () const; + std::vector> **piece_layers** () const; + void **piece_availability** (std\:\:vector& avail) const; + void **prioritize_pieces** (std\:\:vector> const& pieces) const; + download_priority_t **piece_priority** (piece\_index\_t index) const; + std::vector **get_piece_priorities** () const; + void **prioritize_pieces** (std\:\:vector const& pieces) const; + void **piece_priority** (piece\_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; + void **file_priority** (file\_index\_t index, download\_priority\_t priority) const; + std::vector **get_file_priorities** () const; + void **force_dht_announce** () const; + void **force_lsd_announce** () const; + void **force_reannounce** (int seconds = 0, int idx = -1, reannounce\_flags\_t = {}) const; + void **scrape_tracker** (int idx = -1) const; + void **set_download_limit** (int limit) const; + int **download_limit** () const; + int **upload_limit** () const; + void **set_upload_limit** (int 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** (); + int **max_uploads** () const; + void **set_max_uploads** (int max\_uploads) const; + int **max_connections** () const; + void **set_max_connections** (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; + info_hash_t **info_hashes** () 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 **native_handle** () const; + client_data_t **userdata** () const; + bool **in_session** () const; + + 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 file_progress_flags_t **piece_granularity** = 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_handle() +................ + +.. parsed-literal:: + + **torrent_handle** () noexcept = default; + + +constructs a torrent handle that does not refer to a torrent. +i.e. `is_valid()`__ will return false. + + +__ reference-Torrent_Info.html#is_valid() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +add_piece() +........... + +.. parsed-literal:: + + 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. + + +__ reference-Alerts.html#piece_finished_alert +__ reference-Alerts.html#hash_failed_alert +__ reference-Torrent_Status.html#checking_files + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +read_piece() +............ + +.. parsed-literal:: + + 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. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#read_piece_alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +have_piece() +............ + +.. parsed-literal:: + + bool **have_piece** (piece\_index\_t piece) const; + + +Returns true if this piece has been completely downloaded and written +to disk, and false otherwise. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +get_peer_info() +............... + +.. parsed-literal:: + + void **get_peer_info** (std\:\:vector& 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`__. + + +__ reference-Bencoding.html#entry +__ reference-Torrent_Handle.html#torrent_handle +__ reference-Bencoding.html#entry +__ reference-Core.html#peer_info + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +status() +........ + +.. parsed-literal:: + + 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. + + +__ reference-Torrent_Handle.html#torrent_handle +__ reference-Torrent_Status.html#torrent_status +__ reference-Torrent_Status.html#torrent_status + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +get_download_queue() +.................... + +.. parsed-literal:: + + void **get_download_queue** (std\:\:vector& queue) const; + std::vector **get_download_queue** () const; + + +``get_download_queue()`` returns a vector with information about pieces +that are partially downloaded or not downloaded but partially +requested. See `partial_piece_info`__ for the fields in the returned +vector. + + +__ reference-Torrent_Handle.html#partial_piece_info + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +clear_piece_deadlines() set_piece_deadline() reset_piece_deadline() +................................................................... + +.. parsed-literal:: + + void **reset_piece_deadline** (piece\_index\_t index) const; + void **clear_piece_deadlines** () const; + void **set_piece_deadline** (piece\_index\_t index, int deadline, deadline\_flags\_t flags = {}) 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. + + +__ reference-Torrent_Handle.html#read_piece() +__ reference-Torrent_Handle.html#reset_piece_deadline() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_progress() +............... + +.. parsed-literal:: + + void **file_progress** (std\:\:vector& progress, file\_progress\_flags\_t flags = {}) const; + std::vector **file_progress** (file\_progress\_flags\_t flags = {}) const; + + +This function fills in the supplied vector, or returns a 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 currently +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. + + +__ reference-Torrent_Info.html#torrent_info + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file_status() +............. + +.. parsed-literal:: + + std::vector **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`__ + + +__ reference-Custom_Storage.html#open_file_state + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +clear_error() +............. + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +add_tracker() replace_trackers() trackers() +........................................... + +.. parsed-literal:: + + void **add_tracker** (announce\_entry const&) const; + void **replace_trackers** (std\:\:vector const&) const; + std::vector **trackers** () 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. + + +__ reference-Bencoding.html#entry +__ reference-Alerts.html#int +__ reference-Torrent_Handle.html#force_reannounce() +__ reference-Trackers.html#announce_entry +__ reference-Trackers.html#announce_entry + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +url_seeds() add_url_seed() remove_url_seed() +............................................ + +.. parsed-literal:: + + void **remove_url_seed** (std\:\:string const& url) const; + void **add_url_seed** (std\:\:string const& url) const; + std::set **url_seeds** () 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. + + +__ manual-ref.html#http-seeding + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +add_http_seed() remove_http_seed() http_seeds() +............................................... + +.. parsed-literal:: + + std::set **http_seeds** () const; + void **add_http_seed** (std\:\:string const& url) const; + void **remove_http_seed** (std\:\:string const& url) 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. + + +__ manual-ref.html#http-seeding + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +add_extension() +............... + +.. parsed-literal:: + + void **add_extension** ( + std\:\:function(torrent\_handle const&, client\_data\_t)> const& ext + , client\_data\_t userdata = client\_data\_t{}); + + +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. + + +__ reference-Plugins.html#torrent_plugin + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +set_metadata() +.............. + +.. parsed-literal:: + + bool **set_metadata** (span 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +is_valid() +.......... + +.. parsed-literal:: + + 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 +removed from the `session`__ AND destructed. + +To tell if the `torrent_handle`__ is in the `session`__, use +`torrent_handle::in_session()`__. This will return true before +`session_handle::remove_torrent()`__ is called, and false +afterward. + +Clients should only use `is_valid()`__ to determine if the result of +session::find_torrent() was successful. + +Unlike other member functions which return a value, `is_valid()`__ +completes immediately, without blocking on a result from the +network thread. Also unlike other functions, it never throws +the system_error exception. + + +__ reference-Session.html#session +__ reference-Torrent_Handle.html#torrent_handle +__ reference-Session.html#session +__ reference-Torrent_Handle.html#in_session() +__ reference-Session.html#remove_torrent() +__ reference-Torrent_Info.html#is_valid() +__ reference-Torrent_Info.html#is_valid() + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +pause() resume() +................ + +.. parsed-literal:: + + 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`__. + +For possible values of the ``flags`` parameter, see pause_flags_t. + +To know if a torrent is paused or not, call +``torrent_handle::flags()`` and check for the +``torrent_status::paused`` flag. + +.. 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`__. + + + +__ reference-Alerts.html#file_error_alert +__ reference-Session.html#session +__ manual-ref.html#queuing + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +unset_flags() set_flags() flags() +................................. + +.. parsed-literal:: + + torrent_flags_t **flags** () const; + void **set_flags** (torrent\_flags\_t flags, torrent\_flags\_t mask) const; + void **unset_flags** (torrent\_flags\_t flags) const; + void **set_flags** (torrent\_flags\_t flags) 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. + + +__ reference-Core.html#torrent_flags_t +__ reference-Add_Torrent.html#add_torrent_params + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +flush_cache() +............. + +.. parsed-literal:: + + 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. + + +__ reference-Alerts.html#cache_flushed_alert +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +force_recheck() +............... + +.. parsed-literal:: + + 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`__. + + +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +save_resume_data() +.................. + +.. parsed-literal:: + + 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. 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) try + { + torrent_status s = h.status(); + if (!s.has_metadata || !s.need_save_resume_data()) continue; + + h.save_resume_data(); + ++outstanding_resume_data; + } + catch (lt::system_error const& e) + { + // the handle was invalid, ignore this one and move to the next + } + + 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. + + +__ reference-Alerts.html#save_resume_data_alert +__ manual-ref.html#metadata-from-peers +__ reference-Session.html#session +__ reference-Custom_Storage.html#remove_torrent() +__ reference-Alerts.html#save_resume_data_alert +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +need_save_resume_data() +....................... + +.. parsed-literal:: + + 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. + + +__ reference-Alerts.html#save_resume_data_alert +__ reference-Alerts.html#alert + +.. raw:: html + + + + + + + +.. raw:: html + + [report issue] + + + +queue_position() queue_position_down() queue_position_up() queue_position_bottom() queue_position_top() +....................................................................................................... + +.. parsed-literal:: + + void **queue_position_down** () const; + void **queue_position_up** () const; + void **queue_position_top** () const; + void **queue_position_bottom** () const; + queue_position_t **queue_position** () 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. + + +__ reference-Torrent_Status.html#torrent_status + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +queue_position_set() +.................... + +.. parsed-literal:: + + 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 + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +set_ssl_certificate_buffer() set_ssl_certificate() +.................................................. + +.. parsed-literal:: + + void **set_ssl_certificate_buffer** (std\:\:string const& certificate + , std\:\:string const& private\_key + , std\:\:string const& dh\_params); + void **set_ssl_certificate** (std\:\:string const& certificate + , std\:\:string const& private\_key + , std\:\:string const& dh\_params + , std\:\:string const& passphrase = ""); + + +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. + + +__ reference-Torrent_Handle.html#set_ssl_certificate_buffer() +__ reference-Alerts.html#torrent_need_cert_alert + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +torrent_file() torrent_file_with_hashes() +......................................... + +.. parsed-literal:: + + std::shared_ptr **torrent_file_with_hashes** () const; + std::shared_ptr **torrent_file** () const; + + +`torrent_file()`__ 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. + +Note that the `torrent_info`__ object returned here may be a different +instance than the one added to the `session`__, with different attributes +like piece layers, dht nodes and trackers. A `torrent_info`__ object does +not round-trip cleanly when added to a `session`__. + +This means if you want to create a .torrent file by passing the +`torrent_info`__ object into `create_torrent`__, you need to use +`torrent_file_with_hashes()`__ instead. + +`torrent_file_with_hashes()`__ returns a *copy* of the internal +`torrent_info`__ and piece layer hashes (if it's a v2 torrent). The piece +layers will only be included if they are available. If this torrent +was added from a .torrent file with piece layers or if it's seeding, +the piece layers are available. This function is more expensive than +`torrent_file()`__ since it needs to make copies of this information. + +When constructing a `create_torrent`__ object from a `torrent_info`__ that's +in a `session`__, you need to use this function. + +Note that a torrent added from a magnet link may not have the full +merkle trees for all files, and hence not have the complete piece +layers. In that state, you cannot create a .torrent file even from +the `torrent_info`__ returned from `torrent_file_with_hashes()`__. Once the +torrent completes downloading all files, becoming a seed, you can +make a .torrent file from it. + + +__ reference-Torrent_Handle.html#torrent_file() +__ reference-Torrent_Info.html#torrent_info +__ reference-Torrent_Info.html#torrent_info +__ reference-Torrent_Info.html#torrent_info +__ reference-Session.html#session +__ reference-Torrent_Info.html#torrent_info +__ reference-Session.html#session +__ reference-Torrent_Info.html#torrent_info +__ reference-Create_Torrents.html#create_torrent +__ reference-Torrent_Handle.html#torrent_file_with_hashes() +__ reference-Torrent_Handle.html#torrent_file_with_hashes() +__ reference-Torrent_Info.html#torrent_info +__ reference-Torrent_Handle.html#torrent_file() +__ reference-Create_Torrents.html#create_torrent +__ reference-Torrent_Info.html#torrent_info +__ reference-Session.html#session +__ reference-Torrent_Info.html#torrent_info +__ reference-Torrent_Handle.html#torrent_file_with_hashes() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +piece_layers() +.............. + +.. parsed-literal:: + + std::vector> **piece_layers** () const; + + +returns the piece layers for all files in the torrent. If this is a +v1 torrent (and doesn't have any piece layers) it returns an empty +vector. This is a blocking call that will synchronize with the +libtorrent network thread. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +piece_availability() +.................... + +.. parsed-literal:: + + void **piece_availability** (std\:\:vector& avail) const; + + +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. + + + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +get_piece_priorities() piece_priority() prioritize_pieces() +........................................................... + +.. parsed-literal:: + + void **prioritize_pieces** (std\:\:vector> const& pieces) const; + download_priority_t **piece_priority** (piece\_index\_t index) const; + std::vector **get_piece_priorities** () const; + void **prioritize_pieces** (std\:\:vector const& pieces) const; + void **piece_priority** (piece\_index\_t index, download\_priority\_t priority) 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. + + +__ reference-Alerts.html#metadata_received_alert + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +file_priority() prioritize_files() get_file_priorities() +........................................................ + +.. parsed-literal:: + + download_priority_t **file_priority** (file\_index\_t index) const; + void **prioritize_files** (std\:\:vector const& files) const; + void **file_priority** (file\_index\_t index, download\_priority\_t priority) const; + std::vector **get_file_priorities** () 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. +To synchronize with the priorities taking effect, wait for the +`file_prio_alert`__. + +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. + +Moving data from a file into the part file is currently not +supported. If a file has its priority set to 0 *after* it has already +been created, it will not be moved into the partfile. + + +__ reference-Bencoding.html#entry +__ reference-Torrent_Handle.html#piece_priority() +__ reference-Core.html#download_priority_t +__ reference-Torrent_Handle.html#piece_priority() +__ reference-Torrent_Handle.html#prioritize_files() +__ reference-Alerts.html#file_prio_alert + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +force_dht_announce() force_reannounce() force_lsd_announce() +............................................................ + +.. parsed-literal:: + + void **force_dht_announce** () const; + void **force_lsd_announce** () const; + void **force_reannounce** (int seconds = 0, int idx = -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. + +``force_lsd_announce`` will announce the torrent on LSD +immediately. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +scrape_tracker() +................ + +.. parsed-literal:: + + 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`__. + + +__ reference-Torrent_Status.html#torrent_status +__ reference-Alerts.html#scrape_reply_alert +__ reference-Alerts.html#scrape_failed_alert + +.. raw:: html + + + + + + +.. raw:: html + + [report issue] + + + +upload_limit() download_limit() set_upload_limit() set_download_limit() +....................................................................... + +.. parsed-literal:: + + void **set_download_limit** (int limit) const; + int **download_limit** () const; + int **upload_limit** () const; + void **set_upload_limit** (int 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`__. + + +__ manual-ref.html#peer-classes + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +connect_peer() +.............. + +.. parsed-literal:: + + 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`__. + + +__ reference-Core.html#peer_info +__ reference-Core.html#pex_flags_t + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +clear_peers() +............. + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +set_max_uploads() max_uploads() +............................... + +.. parsed-literal:: + + int **max_uploads** () const; + void **set_max_uploads** (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. + + +__ reference-Settings.html#settings_pack + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +set_max_connections() max_connections() +....................................... + +.. parsed-literal:: + + int **max_connections** () const; + void **set_max_connections** (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. + + +__ reference-Settings.html#settings_pack + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +move_storage() +.............. + +.. parsed-literal:: + + 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. + +When copying files, sparse regions are not likely to be preserved. +This makes it proportionally more expensive to move a large torrent +when only few pieces have been downloaded, since the files are then +allocated with zeros in the destination directory. + + +__ reference-Storage.html#move_flags_t + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +rename_file() +............. + +.. parsed-literal:: + + 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. + + +__ reference-Alerts.html#file_renamed_alert +__ reference-Alerts.html#file_rename_failed_alert + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +info_hash() info_hashes() +......................... + +.. parsed-literal:: + + sha1_hash **info_hash** () const; + info_hash_t **info_hashes** () const; + + +returns the info-hash(es) 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. +The ``info_hash()`` returns the SHA-1 info-hash for v1 torrents and a +truncated hash for v2 torrents. For the full v2 info-hash, use +``info_hashes()`` instead. + + + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +operator<() operator!=() operator==() +..................................... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +id() +.... + +.. parsed-literal:: + + std::uint32_t **id** () const; + + +returns a unique identifier for this torrent. It's not a dense index. +It's not preserved across sessions. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +native_handle() +............... + +.. parsed-literal:: + + std::shared_ptr **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. + + +__ reference-Alerts.html#alert +__ reference-Plugins.html#plugin + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +userdata() +.......... + +.. parsed-literal:: + + client_data_t **userdata** () const; + + +returns the userdata pointer as set in `add_torrent_params`__ + + +__ reference-Add_Torrent.html#add_torrent_params + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +in_session() +............ + +.. parsed-literal:: + + bool **in_session** () const; + + +Returns true if the torrent is in the `session`__. It returns true before +session::remove_torrent() is called, and false afterward. + +Note that this is a blocking function, unlike `torrent_handle::is_valid()`__ +which returns immediately. + + +__ reference-Session.html#session +__ reference-Torrent_Handle.html#is_valid() + +.. raw:: html + + + +.. raw:: html + + [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. Using this + flag when adding a piece that is actively being downloaded from other + peers may have some unexpected consequences, as blocks currently + being downloaded from peers may not be replaced. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +query_distributed_copies + calculates ``distributed_copies``, ``distributed_full_copies`` and + ``distributed_fraction``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +query_accurate_download_counters + includes partial downloaded blocks in ``total_done`` and + ``total_wanted_done``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +query_last_seen_complete + includes ``last_seen_complete``. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +query_pieces + populate the ``pieces`` field in `torrent_status`__. + + +__ reference-Torrent_Status.html#torrent_status + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +query_verified_pieces + includes ``verified_pieces`` (only applies to torrents in *seed + mode*). + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +query_torrent_file + includes ``torrent_file``, which is all the static information from + the .torrent file. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +query_save_path + includes ``save_path``, the path to the directory the files of the + torrent are saved to. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + +__ reference-Alerts.html#alert +__ reference-Alerts.html#read_piece_alert +__ reference-Alerts.html#alert + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +piece_granularity + 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. + + +__ reference-Torrent_Handle.html#file_progress() + +.. raw:: html + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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). + + + +.. raw:: html + + + +.. raw:: html + + [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. + + +__ reference-Alerts.html#save_resume_data_failed_alert + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +hash_value() +------------ + +Declared in "`libtorrent/torrent_handle.hpp`__" + + +__ include/libtorrent/torrent_handle.hpp + +.. parsed-literal:: + + std::size_t **hash_value** (torrent\_handle const& h); + + +for std::hash (and to support using this type in unordered_map etc.) + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +add_torrent_params +------------------ + +Declared in "`libtorrent/add_torrent_params.hpp`__" + + +__ include/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()`__. + + +__ reference-Add_Torrent.html#add_torrent_params +__ reference-Session.html#session +__ reference-Torrent_Info.html#torrent_info +__ reference-Add_Torrent.html#add_torrent_params +__ reference-Session.html#session +__ reference-Resume_Data.html#read_resume_data() +__ reference-Resume_Data.html#write_resume_data() + + +.. parsed-literal:: + + + struct add_torrent_params + { + int **version** = LIBTORRENT_VERSION_NUM; + std::shared_ptr ti; + aux::noexcept_movable> trackers; + aux::noexcept_movable> tracker_tiers; + aux::noexcept_movable>> dht_nodes; + std::string name; + std::string save_path; + storage_mode_t **storage_mode** = storage_mode_sparse; + client_data_t userdata; + aux::noexcept_movable> file_priorities; + std::string trackerid; + torrent_flags_t **flags** = torrent_flags::default_flags; + info_hash_t info_hashes; + 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> http_seeds; + aux::noexcept_movable> url_seeds; + aux::noexcept_movable> peers; + aux::noexcept_movable> banned_peers; + aux::noexcept_movable> unfinished_pieces; + typed_bitfield have_pieces; + typed_bitfield verified_pieces; + aux::noexcept_movable> piece_priorities; + aux::vector, file_index_t> merkle_trees; + aux::vector, file_index_t> merkle_tree_mask; + aux::vector, file_index_t> verified_leaf_hashes; + aux::noexcept_movable> renamed_files; + std::time_t **last_download** = 0; + std::time_t **last_upload** = 0; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +version + filled in by the constructor and should be left untouched. It is used + for forward binary compatibility. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +ti + `torrent_info`__ object with the torrent to add. Unless the + info_hash is set, this is required to be initialized. + + +__ reference-Torrent_Info.html#torrent_info + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + +__ reference-Session.html#session + +.. raw:: html + + + +.. raw:: html + + [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. + + +__ reference-Torrent_Info.html#torrent_info + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +storage_mode + One of the values from `storage_mode_t`__. For more information, see + `storage allocation`__. + + +__ reference-Storage.html#storage_mode_t +__ manual-ref.html#storage-allocation + +.. raw:: html + + + +.. raw:: html + + [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()`__). It will also be stored in the + torrent object and can be retrieved by calling `userdata()`__. + + +__ reference-Torrent_Handle.html#add_extension() +__ reference-Torrent_Handle.html#userdata() + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + +__ reference-Core.html#torrent_flags_t + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +info_hashes + 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. + + +__ reference-Core.html#parse_magnet_uri() +__ reference-Add_Torrent.html#add_torrent_params + +.. raw:: html + + + + +.. raw:: html + + [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`__. + + +__ reference-Torrent_Handle.html#torrent_handle +__ reference-Torrent_Handle.html#torrent_handle +__ manual-ref.html#peer-classes + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + +upload_limit download_limit + the upload and download rate limits for this torrent, specified in + bytes per second. -1 means unlimited. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + +total_uploaded total_downloaded + the total number of bytes uploaded and downloaded by this torrent so + far. + + + +.. raw:: html + + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + + +.. raw:: html + + [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. + + +__ reference-Session.html#add_torrent() + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + + +.. raw:: html + + [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`_. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +peers + peers to add to the torrent, to be tried to be connected to as + bittorrent peers. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +banned_peers + peers banned from this torrent. The will not be connected to + + + +.. raw:: html + + + +.. raw:: html + + [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. + + +__ reference-Utility.html#bitfield + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +have_pieces + this is a `bitfield`__ indicating which pieces we already have of this + torrent. + + +__ reference-Utility.html#bitfield + +.. raw:: html + + + +.. raw:: html + + [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. + + +__ reference-Utility.html#bitfield + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +merkle_trees + v2 hashes, if known + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +merkle_tree_mask + if set, indicates which hashes are included in the corresponding + vector of ``merkle_trees``. These bitmasks always cover the full + tree, a cleared bit means the hash is all zeros (i.e. not set) and + set bit means the next hash in the corresponding vector in + ``merkle_trees`` is the hash for that node. This is an optimization + to avoid storing a lot of zeros. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +verified_leaf_hashes + bit-fields indicating which v2 leaf hashes have been verified + against the root hash. If this vector is empty and merkle_trees is + non-empty it implies that all hashes in merkle_trees are verified. + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + +last_download last_upload + the posix time of the last time payload was received or sent for this + torrent, respectively. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +client_data_t +------------- + +Declared in "`libtorrent/client_data.hpp`__" + + +__ include/libtorrent/client_data.hpp + +A thin wrapper around a void pointer used as "user data". i.e. an opaque +cookie passed in to libtorrent and returned on demand. It adds type-safety by +requiring the same type be requested out of it as was assigned to it. + + + + +.. parsed-literal:: + + + struct client_data_t + { + **client_data_t** () = default; + explicit **client_data_t** (T\* v); + client_data_t& **operator=** (T\* v); + T\* **get** () const; + explicit operator **T** () const; + client_data_t& **operator=** (void\*) = delete; + client_data_t& **operator=** (void const\*) = delete; + operator void **const*** () const = delete; + operator **void*** () const = delete; + + template ::value>::type> + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +client_data_t() +............... + +.. parsed-literal:: + + **client_data_t** () = default; + + +construct a nullptr client data + + + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +operator=() const*() void*() +............................ + +.. parsed-literal:: + + client_data_t& **operator=** (void\*) = delete; + client_data_t& **operator=** (void const\*) = delete; + operator void **const*** () const = delete; + operator **void*** () const = delete; + + +we don't allow type-unsafe operations + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +web_seed_entry +-------------- + +Declared in "`libtorrent/torrent_info.hpp`__" + + +__ include/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`_. + + +__ reference-Torrent_Info.html#web_seed_entry + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +operator==() +............ + +.. parsed-literal:: + + bool **operator==** (web\_seed\_entry const& e) const; + + +URL and type comparison + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +operator<() +........... + +.. parsed-literal:: + + bool **operator<** (web\_seed\_entry const& e) const; + + +URL and type less-than comparison + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum type_t +........... + +Declared in "`libtorrent/torrent_info.hpp`__" + + +__ include/libtorrent/torrent_info.hpp + ++-----------+-------+-------------+ +| name | value | description | ++===========+=======+=============+ +| url_seed | 0 | | ++-----------+-------+-------------+ +| http_seed | 1 | | ++-----------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +url + The URL of the web seed + + + +.. raw:: html + + + +.. raw:: html + + [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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +extra_headers + Any extra HTTP headers that need to be passed to the web seed + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +type + The type of web seed (see `type_t`__) + + +__ reference-Torrent_Info.html#type_t + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +load_torrent_limits +------------------- + +Declared in "`libtorrent/torrent_info.hpp`__" + + +__ include/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. + + + + +.. parsed-literal:: + + + struct load_torrent_limits + { + int **max_buffer_size** = 10000000; + int **max_pieces** = 0x200000; + int **max_decode_depth** = 100; + int **max_decode_tokens** = 3000000; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +max_buffer_size + the max size of a .torrent file to load into RAM + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +max_pieces + the max number of pieces allowed in the torrent + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +max_decode_depth + the max recursion depth in the bdecoded structure + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +max_decode_tokens + the max number of bdecode tokens + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_info +------------ + +Declared in "`libtorrent/torrent_info.hpp`__" + + +__ include/libtorrent/torrent_info.hpp + +the `torrent_info`__ class holds the information found in a .torrent file. + + +__ reference-Torrent_Info.html#torrent_info + + +.. parsed-literal:: + + + class torrent_info + { + explicit **torrent_info** (span buffer, from\_span\_t); + **torrent_info** (bdecode\_node const& torrent\_file, error\_code& ec); + **torrent_info** (torrent\_info const& t); + explicit **torrent_info** (std\:\:string const& filename); + **torrent_info** (char const\* buffer, int size); + **torrent_info** (char const\* buffer, int size, error\_code& ec); + **torrent_info** (std\:\:string const& filename, error\_code& ec); + explicit **torrent_info** (info\_hash\_t const& info\_hash); + **torrent_info** (span buffer, error\_code& ec, from\_span\_t); + **torrent_info** (bdecode\_node const& torrent\_file, load\_torrent\_limits const& cfg); + **torrent_info** (span buffer, load\_torrent\_limits const& cfg, from\_span\_t); + **torrent_info** (std\:\:string const& filename, load\_torrent\_limits const& cfg); + explicit **torrent_info** (bdecode\_node const& torrent\_file); + **~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 = 0); + std::vector const& **trackers** () const; + void **clear_trackers** (); + void **add_tracker** (std\:\:string const& url, int tier + , announce\_entry\:\:tracker\_source source); + std::vector **similar_torrents** () const; + std::vector **collections** () const; + void **add_url_seed** (std\:\:string const& url + , std\:\:string const& ext\_auth = std\:\:string() + , web\_seed\_entry\:\:headers\_t const& ext\_headers = web\_seed\_entry\:\:headers\_t()); + std::vector const& **web_seeds** () const; + void **set_web_seeds** (std\:\:vector seeds); + 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::int64_t **total_size** () const; + int **piece_length** () const; + int **num_pieces** () const; + index_range **piece_range** () const; + piece_index_t **end_piece** () const; + piece_index_t **last_piece** () const; + info_hash_t const& **info_hashes** () const; + sha1_hash **info_hash** () const noexcept; + bool **v1** () const; + bool **v2** () const; + int **num_files** () const; + std::vector **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; + const std::string& **name** () const; + std::time_t **creation_date** () const; + const std::string& **creator** () const; + const std::string& **comment** () const; + std::vector> const& **nodes** () const; + void **add_node** (std\:\:pair const& node); + bool **parse_info_section** (bdecode\_node const& info, error\_code& ec, int max\_pieces); + bdecode_node **info** (char const\* key) const; + span **info_section** () const; + span **piece_layer** (file\_index\_t) const; + void **free_piece_layers** (); + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +torrent_info() +.............. + +.. parsed-literal:: + + explicit **torrent_info** (span buffer, from\_span\_t); + **torrent_info** (bdecode\_node const& torrent\_file, error\_code& ec); + **torrent_info** (torrent\_info const& t); + explicit **torrent_info** (std\:\:string const& filename); + **torrent_info** (char const\* buffer, int size); + **torrent_info** (char const\* buffer, int size, error\_code& ec); + **torrent_info** (std\:\:string const& filename, error\_code& ec); + explicit **torrent_info** (info\_hash\_t const& info\_hash); + **torrent_info** (span buffer, error\_code& ec, from\_span\_t); + **torrent_info** (bdecode\_node const& torrent\_file, load\_torrent\_limits const& cfg); + **torrent_info** (span buffer, load\_torrent\_limits const& cfg, from\_span\_t); + **torrent_info** (std\:\:string const& filename, load\_torrent\_limits const& cfg); + explicit **torrent_info** (bdecode\_node const& torrent\_file); + + +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``. + + +__ reference-Bdecoding.html#bdecode_node +__ reference-Torrent_Info.html#torrent_info +__ reference-Bdecoding.html#bdecode_node +__ reference-Bdecoding.html#bdecode_node +__ reference-Bdecoding.html#bdecode() +__ reference-Torrent_Info.html#torrent_info +__ reference-Torrent_Info.html#torrent_info + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +~torrent_info() +............... + +.. parsed-literal:: + + **~torrent_info** (); + + +frees all storage associated with this `torrent_info`__ object + + +__ reference-Torrent_Info.html#torrent_info + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +orig_files() files() +.................... + +.. parsed-literal:: + + 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. + + +__ reference-Storage.html#file_storage +__ reference-Torrent_Info.html#torrent_info +__ reference-Storage.html#file_storage +__ reference-Storage.html#file_storage + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +rename_file() +............. + +.. parsed-literal:: + + 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. + + +__ reference-Torrent_Info.html#files() +__ reference-Torrent_Info.html#orig_files() +__ reference-Torrent_Handle.html#move_storage() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +remap_files() +............. + +.. parsed-literal:: + + void **remap_files** (file\_storage const& f); + + +.. warning:: + Using `remap_files()` is discouraged as it's incompatible with v2 + torrents. This is because the piece boundaries and piece hashes in + v2 torrents are intimately tied to the file boundaries. Instead, + just rename individual files, or implement a custom `disk_interface`__ + to customize how to store files. + +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. + + +__ reference-Custom_Storage.html#disk_interface + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +clear_trackers() trackers() add_tracker() +......................................... + +.. parsed-literal:: + + void **add_tracker** (std\:\:string const& url, int tier = 0); + std::vector const& **trackers** () const; + void **clear_trackers** (); + void **add_tracker** (std\:\:string const& url, int tier + , announce\_entry\:\:tracker\_source source); + + +``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`__. + +``trackers()`` returns all entries from announce-list. + +``clear_trackers()`` removes all trackers from announce-list. + + +__ reference-Trackers.html#announce_entry +__ reference-Bencoding.html#entry +__ reference-Trackers.html#announce_entry + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +similar_torrents() collections() +................................ + +.. parsed-literal:: + + std::vector **similar_torrents** () const; + std::vector **collections** () 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. + + + +.. raw:: html + + + + + + +.. raw:: html + + [report issue] + + + +add_http_seed() set_web_seeds() add_url_seed() web_seeds() +.......................................................... + +.. parsed-literal:: + + void **add_url_seed** (std\:\:string const& url + , std\:\:string const& ext\_auth = std\:\:string() + , web\_seed\_entry\:\:headers\_t const& ext\_headers = web\_seed\_entry\:\:headers\_t()); + std::vector const& **web_seeds** () const; + void **set_web_seeds** (std\:\:vector seeds); + 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()); + + +``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. + +``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. + + +__ reference-Bencoding.html#entry +__ manual-ref.html#http-seeding + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +total_size() +............ + +.. parsed-literal:: + + std::int64_t **total_size** () const; + + +``total_size()`` returns the total number of bytes the torrent-file +represents. Note that this is the number of pieces times the piece +size (modulo the last piece possibly being smaller). With pad files, +the total size will be larger than the sum of all (regular) file +sizes. + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +num_pieces() piece_length() +........................... + +.. parsed-literal:: + + int **piece_length** () const; + int **num_pieces** () const; + + +``piece_length()`` and ``num_pieces()`` returns 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. + + + +.. raw:: html + + + + + +.. raw:: html + + [report issue] + + + +last_piece() end_piece() piece_range() +...................................... + +.. parsed-literal:: + + index_range **piece_range** () const; + piece_index_t **end_piece** () const; + piece_index_t **last_piece** () 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`__. + + +__ reference-Storage.html#file_storage + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +info_hash() info_hashes() +......................... + +.. parsed-literal:: + + info_hash_t const& **info_hashes** () const; + sha1_hash **info_hash** () const noexcept; + + +returns the info-hash of the torrent. For BitTorrent v2 support, use +``info_hashes()`` to get an object that may hold both a v1 and v2 +info-hash + + + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +v2() v1() +......... + +.. parsed-literal:: + + bool **v1** () const; + bool **v2** () const; + + +returns whether this torrent has v1 and/or v2 metadata, respectively. +Hybrid torrents have both. These are shortcuts for +info_hashes().has_v1() and info_hashes().has_v2() calls. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +num_files() +........... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +map_block() +........... + +.. parsed-literal:: + + std::vector **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`__. + + +__ reference-Storage.html#file_slice + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +map_file() +.......... + +.. parsed-literal:: + + 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()``. + + +__ reference-Core.html#peer_request + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +ssl_cert() +.......... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +is_valid() +.......... + +.. parsed-literal:: + + 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. + + +__ reference-Torrent_Info.html#torrent_info + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +priv() +...... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +is_i2p() +........ + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +piece_size() +............ + +.. parsed-literal:: + + 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. + + +__ reference-Torrent_Info.html#piece_length() + +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +hash_for_piece_ptr() hash_for_piece() +..................................... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +name() +...... + +.. parsed-literal:: + + const std::string& **name** () const; + + +``name()`` returns the name of the torrent. +name contains UTF-8 encoded string. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +creation_date() +............... + +.. parsed-literal:: + + 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, 0 is +returned. +.. _`posix time`: http://www.opengroup.org/onlinepubs/009695399/functions/time.html + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +creator() +......... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +comment() +......... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +nodes() +....... + +.. parsed-literal:: + + std::vector> 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). + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +add_node() +.......... + +.. parsed-literal:: + + void **add_node** (std\:\:pair 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +parse_info_section() +.................... + +.. parsed-literal:: + + bool **parse_info_section** (bdecode\_node const& info, error\_code& ec, int max\_pieces); + + +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 `max_pieces` parameter allows limiting the amount of memory +dedicated to loading the torrent, and fails for torrents that exceed +the limit. To load large torrents, this limit may also need to be +raised in `settings_pack::max_piece_count`__ and in calls to +`read_resume_data()`__. + + +__ reference-Torrent_Info.html#torrent_info +__ reference-Bdecoding.html#bdecode_node +__ reference-Settings.html#max_piece_count +__ reference-Resume_Data.html#read_resume_data() + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +info() +...... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +info_section() +.............. + +.. parsed-literal:: + + span **info_section** () const; + + +returns a the raw info section of the torrent file. +The underlying buffer is still owned by the `torrent_info`__ object + + +__ reference-Torrent_Info.html#torrent_info + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +piece_layer() +............. + +.. parsed-literal:: + + span **piece_layer** (file\_index\_t) const; + + +return the bytes of the piece layer hashes for the specified file. If +the file doesn't have a piece layer, an empty span is returned. +The span size is divisible by 32, the size of a SHA-256 hash. +If the size of the file is smaller than or equal to the piece size, +the files "root hash" is the hash of the file and is not saved +separately in the "piece layers" field, but this function still +returns the root hash of the file in that case. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +free_piece_layers() +................... + +.. parsed-literal:: + + void **free_piece_layers** (); + + +clears the piece layers from the `torrent_info`__. This is done by the +`session`__ when a torrent is added, to avoid storing it twice. The piece +layer (or other hashes part of the merkle tree) are stored in the +internal torrent object. + + +__ reference-Torrent_Info.html#torrent_info +__ reference-Session.html#session + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +storage_error +------------- + +Declared in "`libtorrent/error_code.hpp`__" + + +__ include/libtorrent/error_code.hpp + +used by storage to return errors +also includes which underlying file the +error happened on + + + + +.. parsed-literal:: + + + struct storage_error + { + explicit operator **bool** () const; + file_index_t **file** () const; + void **file** (file\_index\_t f); + + error_code ec; + operation_t operation; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +bool() +...... + +.. parsed-literal:: + + explicit operator **bool** () const; + + +explicitly converts to true if this object represents an error, and +false if it does not. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +file() +...... + +.. parsed-literal:: + + 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`__. + + +__ reference-Torrent_Status.html#torrent_status + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +ec + the error that occurred + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +operation + A code from `operation_t`__ enum, indicating what + kind of operation failed. + + +__ reference-Alerts.html#operation_t + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +bdecode_category() +------------------ + +Declared in "`libtorrent/bdecode.hpp`__" + + +__ include/libtorrent/bdecode.hpp + +.. parsed-literal:: + + boost::system::error_category& **bdecode_category** (); + + + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +pcp_category() +-------------- + +Declared in "`libtorrent/natpmp.hpp`__" + + +__ include/libtorrent/natpmp.hpp + +.. parsed-literal:: + + boost::system::error_category& **pcp_category** (); + + + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +socks_category() +---------------- + +Declared in "`libtorrent/socks5_stream.hpp`__" + + +__ include/libtorrent/socks5_stream.hpp + +.. parsed-literal:: + + boost::system::error_category& **socks_category** (); + + +returns the error_category for SOCKS5 errors + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +i2p_category() +-------------- + +Declared in "`libtorrent/i2p_stream.hpp`__" + + +__ include/libtorrent/i2p_stream.hpp + +.. parsed-literal:: + + boost::system::error_category& **i2p_category** (); + + +returns the error category for I2P errors + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +upnp_category() +--------------- + +Declared in "`libtorrent/upnp.hpp`__" + + +__ include/libtorrent/upnp.hpp + +.. parsed-literal:: + + boost::system::error_category& **upnp_category** (); + + +the boost.system error category for UPnP errors + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +gzip_category() +--------------- + +Declared in "`libtorrent/gzip.hpp`__" + + +__ include/libtorrent/gzip.hpp + +.. parsed-literal:: + + boost::system::error_category& **gzip_category** (); + + +get the ``error_category`` for zip errors + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +libtorrent_category() +--------------------- + +Declared in "`libtorrent/error_code.hpp`__" + + +__ include/libtorrent/error_code.hpp + +.. parsed-literal:: + + boost::system::error_category& **libtorrent_category** (); + + +return the instance of the libtorrent_error_category which +maps libtorrent error codes to human readable error messages. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +http_category() +--------------- + +Declared in "`libtorrent/error_code.hpp`__" + + +__ include/libtorrent/error_code.hpp + +.. parsed-literal:: + + boost::system::error_category& **http_category** (); + + +returns the error_category for HTTP errors + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum error_code_enum +-------------------- + +Declared in "`libtorrent/bdecode.hpp`__" + + +__ include/libtorrent/bdecode.hpp + ++----------------+-------+-------------------------------------------------------------------+ +| name | value | description | ++================+=======+===================================================================+ +| no_error | 0 | Not an error | +| | | | ++----------------+-------+-------------------------------------------------------------------+ +| expected_digit | 1 | expected digit in bencoded string | +| | | | ++----------------+-------+-------------------------------------------------------------------+ +| expected_colon | 2 | expected colon in bencoded string | +| | | | ++----------------+-------+-------------------------------------------------------------------+ +| unexpected_eof | 3 | unexpected end of file in bencoded string | +| | | | ++----------------+-------+-------------------------------------------------------------------+ +| expected_value | 4 | expected value (list, dict, `int`__ or string) in bencoded string | +| | | | ++----------------+-------+-------------------------------------------------------------------+ +| depth_exceeded | 5 | bencoded recursion depth limit exceeded | +| | | | ++----------------+-------+-------------------------------------------------------------------+ +| limit_exceeded | 6 | bencoded item count limit exceeded | +| | | | ++----------------+-------+-------------------------------------------------------------------+ +| overflow | 7 | integer overflow | +| | | | ++----------------+-------+-------------------------------------------------------------------+ +| error_code_max | 8 | the number of error codes | +| | | | ++----------------+-------+-------------------------------------------------------------------+ + + +__ reference-Alerts.html#int + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum pcp_errors +--------------- + +Declared in "`libtorrent/natpmp.hpp`__" + + +__ include/libtorrent/natpmp.hpp + ++-----------------------------+-------+-------------+ +| name | value | description | ++=============================+=======+=============+ +| pcp_success | 0 | | ++-----------------------------+-------+-------------+ +| pcp_unsupp_version | 1 | | ++-----------------------------+-------+-------------+ +| pcp_not_authorized | 2 | | ++-----------------------------+-------+-------------+ +| pcp_malformed_request | 3 | | ++-----------------------------+-------+-------------+ +| pcp_unsupp_opcode | 4 | | ++-----------------------------+-------+-------------+ +| pcp_unsupp_option | 5 | | ++-----------------------------+-------+-------------+ +| pcp_malformed_option | 6 | | ++-----------------------------+-------+-------------+ +| pcp_network_failure | 7 | | ++-----------------------------+-------+-------------+ +| pcp_no_resources | 8 | | ++-----------------------------+-------+-------------+ +| pcp_unsupp_protocol | 9 | | ++-----------------------------+-------+-------------+ +| pcp_user_ex_quota | 10 | | ++-----------------------------+-------+-------------+ +| pcp_cannot_provide_external | 11 | | ++-----------------------------+-------+-------------+ +| pcp_address_mismatch | 12 | | ++-----------------------------+-------+-------------+ +| pcp_excessive_remote_peers | 13 | | ++-----------------------------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum socks_error_code +--------------------- + +Declared in "`libtorrent/socks5_stream.hpp`__" + + +__ include/libtorrent/socks5_stream.hpp + ++------------------------------------+-------+-------------+ +| name | value | description | ++====================================+=======+=============+ +| no_error | 0 | | ++------------------------------------+-------+-------------+ +| unsupported_version | 1 | | ++------------------------------------+-------+-------------+ +| unsupported_authentication_method | 2 | | ++------------------------------------+-------+-------------+ +| unsupported_authentication_version | 3 | | ++------------------------------------+-------+-------------+ +| authentication_error | 4 | | ++------------------------------------+-------+-------------+ +| username_required | 5 | | ++------------------------------------+-------+-------------+ +| general_failure | 6 | | ++------------------------------------+-------+-------------+ +| command_not_supported | 7 | | ++------------------------------------+-------+-------------+ +| no_identd | 8 | | ++------------------------------------+-------+-------------+ +| identd_error | 9 | | ++------------------------------------+-------+-------------+ +| num_errors | 10 | | ++------------------------------------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum i2p_error_code +------------------- + +Declared in "`libtorrent/i2p_stream.hpp`__" + + +__ include/libtorrent/i2p_stream.hpp + ++-----------------+-------+-------------+ +| name | value | description | ++=================+=======+=============+ +| no_error | 0 | | ++-----------------+-------+-------------+ +| parse_failed | 1 | | ++-----------------+-------+-------------+ +| cant_reach_peer | 2 | | ++-----------------+-------+-------------+ +| i2p_error | 3 | | ++-----------------+-------+-------------+ +| invalid_key | 4 | | ++-----------------+-------+-------------+ +| invalid_id | 5 | | ++-----------------+-------+-------------+ +| timeout | 6 | | ++-----------------+-------+-------------+ +| key_not_found | 7 | | ++-----------------+-------+-------------+ +| duplicated_id | 8 | | ++-----------------+-------+-------------+ +| num_errors | 9 | | ++-----------------+-------+-------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum error_code_enum +-------------------- + +Declared in "`libtorrent/upnp.hpp`__" + + +__ include/libtorrent/upnp.hpp + ++------------------------------------+-------+-------------------------------------------------------+ +| name | value | description | ++====================================+=======+=======================================================+ +| no_error | 0 | No error | +| | | | ++------------------------------------+-------+-------------------------------------------------------+ +| invalid_argument | 402 | One of the arguments in the request is invalid | +| | | | ++------------------------------------+-------+-------------------------------------------------------+ +| action_failed | 501 | The request failed | +| | | | ++------------------------------------+-------+-------------------------------------------------------+ +| value_not_in_array | 714 | The specified value does not exist in the array | +| | | | ++------------------------------------+-------+-------------------------------------------------------+ +| source_ip_cannot_be_wildcarded | 715 | The source IP address cannot be wild-carded, but | +| | | must be fully specified | +| | | | ++------------------------------------+-------+-------------------------------------------------------+ +| external_port_cannot_be_wildcarded | 716 | The external port cannot be a wildcard, but must | +| | | be specified | +| | | | ++------------------------------------+-------+-------------------------------------------------------+ +| port_mapping_conflict | 718 | The port mapping `entry`__ specified conflicts with a | +| | | mapping assigned previously to another client | +| | | | ++------------------------------------+-------+-------------------------------------------------------+ +| internal_port_must_match_external | 724 | Internal and external port value must be the same | +| | | | ++------------------------------------+-------+-------------------------------------------------------+ +| only_permanent_leases_supported | 725 | The NAT implementation only supports permanent | +| | | lease times on port mappings | +| | | | ++------------------------------------+-------+-------------------------------------------------------+ +| remote_host_must_be_wildcard | 726 | RemoteHost must be a wildcard and cannot be a | +| | | specific IP address or DNS name | +| | | | ++------------------------------------+-------+-------------------------------------------------------+ +| external_port_must_be_wildcard | 727 | ExternalPort must be a wildcard and cannot be a | +| | | specific port | +| | | | ++------------------------------------+-------+-------------------------------------------------------+ + + +__ reference-Bencoding.html#entry + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum error_code_enum +-------------------- + +Declared in "`libtorrent/gzip.hpp`__" + + +__ include/libtorrent/gzip.hpp + ++-------------------------------------+-------+---------------------------------------------------------------------+ +| name | value | description | ++=====================================+=======+=====================================================================+ +| no_error | 0 | Not an error | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| invalid_gzip_header | 1 | the supplied gzip buffer has invalid header | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| inflated_data_too_large | 2 | the gzip buffer would inflate to more bytes than the specified | +| | | maximum size, and was rejected. | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| data_did_not_terminate | 3 | available inflate data did not terminate | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| space_exhausted | 4 | output space exhausted before completing inflate | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| invalid_block_type | 5 | invalid block type (type == 3) | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| invalid_stored_block_length | 6 | stored block length did not match one's complement | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| too_many_length_or_distance_codes | 7 | dynamic block code description: too many length or distance codes | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| code_lengths_codes_incomplete | 8 | dynamic block code description: code lengths codes incomplete | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| repeat_lengths_with_no_first_length | 9 | dynamic block code description: repeat lengths with no first length | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| repeat_more_than_specified_lengths | 10 | dynamic block code description: repeat more than specified lengths | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| invalid_literal_length_code_lengths | 11 | dynamic block code description: invalid literal/length code lengths | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| invalid_distance_code_lengths | 12 | dynamic block code description: invalid distance code lengths | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| invalid_literal_code_in_block | 13 | invalid literal/length or distance code in fixed or dynamic block | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| distance_too_far_back_in_block | 14 | distance is too far back in fixed or dynamic block | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| unknown_gzip_error | 15 | an unknown error occurred during gzip inflation | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ +| error_code_max | 16 | the number of error codes | +| | | | ++-------------------------------------+-------+---------------------------------------------------------------------+ + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum error_code_enum +-------------------- + +Declared in "`libtorrent/error_code.hpp`__" + + +__ include/libtorrent/error_code.hpp + ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| name | value | description | ++======================================+=======+===========================================================================+ +| no_error | 0 | Not an error | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| file_collision | 1 | Two torrents has files which end up overwriting each other | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| failed_hash_check | 2 | A piece did not match its piece hash | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_is_no_dict | 3 | The .torrent file does not contain a bencoded dictionary at | +| | | its top level | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_missing_info | 4 | The .torrent file does not have an ``info`` dictionary | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_info_no_dict | 5 | The .torrent file's ``info`` `entry`__ is not a dictionary | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_missing_piece_length | 6 | The .torrent file does not have a ``piece length`` `entry`__ | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_missing_name | 7 | The .torrent file does not have a ``name`` `entry`__ | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_invalid_name | 8 | The .torrent file's name `entry`__ is invalid | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_invalid_length | 9 | The length of a file, or of the whole .torrent file is invalid. | +| | | Either negative or not an integer | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_file_parse_failed | 10 | Failed to parse a file `entry`__ in the .torrent | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_missing_pieces | 11 | The ``pieces`` field is missing or invalid in the .torrent file | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_invalid_hashes | 12 | The ``pieces`` string has incorrect length | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| too_many_pieces_in_torrent | 13 | The .torrent file has more pieces than is supported by libtorrent | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_swarm_metadata | 14 | The metadata (.torrent file) that was received from the swarm | +| | | matched the info-hash, but failed to be parsed | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_bencoding | 15 | The file or buffer is not correctly bencoded | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| no_files_in_torrent | 16 | The .torrent file does not contain any files | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_escaped_string | 17 | The string was not properly url-encoded as expected | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| session_is_closing | 18 | Operation is not permitted since the `session`__ is shutting down | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| duplicate_torrent | 19 | There's already a torrent with that info-hash added to the | +| | | `session`__ | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_torrent_handle | 20 | The supplied `torrent_handle`__ is not referring to a valid torrent | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_entry_type | 21 | The type requested from the `entry`__ did not match its type | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| missing_info_hash_in_uri | 22 | The specified URI does not contain a valid info-hash | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| file_too_short | 23 | One of the files in the torrent was unexpectedly small. This | +| | | might be caused by files being changed by an external process | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| unsupported_url_protocol | 24 | The 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_error | 25 | The URL did not conform to URL syntax and failed to be parsed | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| peer_sent_empty_piece | 26 | The peer sent a piece message of length 0 | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| parse_failed | 27 | A bencoded structure was corrupt and failed to be parsed | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_file_tag | 28 | The fast resume file was missing or had an invalid file version | +| | | tag | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| missing_info_hash | 29 | The fast resume file was missing or had an invalid info-hash | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| mismatching_info_hash | 30 | The info-hash did not match the torrent | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_hostname | 31 | The URL contained an invalid hostname | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_port | 32 | The URL had an invalid port | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| port_blocked | 33 | The port is blocked by the port-filter, and prevented the | +| | | connection | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| expected_close_bracket_in_address | 34 | The IPv6 address was expected to end with "]" | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| destructing_torrent | 35 | The torrent is being destructed, preventing the operation to | +| | | succeed | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| timed_out | 36 | The connection timed out | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| upload_upload_connection | 37 | The peer is upload only, and we are upload only. There's no point | +| | | in keeping the connection | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| uninteresting_upload_peer | 38 | The peer is upload only, and we're not interested in it. There's | +| | | no point in keeping the connection | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_info_hash | 39 | The peer sent an unknown info-hash | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_paused | 40 | The torrent is paused, preventing the operation from succeeding | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_have | 41 | The peer sent an invalid have message, either wrong size or | +| | | referring to a piece that doesn't exist in the torrent | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_bitfield_size | 42 | The `bitfield`__ message had the incorrect size | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| too_many_requests_when_choked | 43 | The peer kept requesting pieces after it was choked, possible | +| | | abuse attempt. | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_piece | 44 | The peer sent a piece message that does not correspond to a | +| | | piece request sent by the client | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| no_memory | 45 | memory allocation failed | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_aborted | 46 | The torrent is aborted, preventing the operation to succeed | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| self_connection | 47 | The peer is a connection to ourself, no point in keeping it | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_piece_size | 48 | The peer sent a piece message with invalid size, either negative | +| | | or greater than one block | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| timed_out_no_interest | 49 | The peer has not been interesting or interested in us for too | +| | | long, no point in keeping it around | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| timed_out_inactivity | 50 | The peer has not said anything in a long time, possibly dead | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| timed_out_no_handshake | 51 | The peer did not send a handshake within a reasonable amount of | +| | | time, it might not be a bittorrent peer | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| timed_out_no_request | 52 | The peer has been unchoked for too long without requesting any | +| | | data. It might be lying about its interest in us | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_choke | 53 | The peer sent an invalid choke message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_unchoke | 54 | The peer send an invalid unchoke message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_interested | 55 | The peer sent an invalid interested message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_not_interested | 56 | The peer sent an invalid not-interested message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_request | 57 | The peer sent an invalid piece request message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_hash_list | 58 | The peer sent an invalid hash-list message (this is part of the | +| | | merkle-torrent extension) | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_hash_piece | 59 | The peer sent an invalid hash-piece message (this is part of the | +| | | merkle-torrent extension) | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_cancel | 60 | The peer sent an invalid cancel message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_dht_port | 61 | The peer sent an invalid DHT port-message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_suggest | 62 | The peer sent an invalid suggest piece-message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_have_all | 63 | The peer sent an invalid have all-message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_have_none | 64 | The peer sent an invalid have none-message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_reject | 65 | The peer sent an invalid reject message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_allow_fast | 66 | The peer sent an invalid allow fast-message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_extended | 67 | The peer sent an invalid extension message ID | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_message | 68 | The peer sent an invalid message ID | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| sync_hash_not_found | 69 | The synchronization hash was not found in the encrypted handshake | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_encryption_constant | 70 | The encryption constant in the handshake is invalid | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| no_plaintext_mode | 71 | The peer does not support plain text, which is the selected mode | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| no_rc4_mode | 72 | The peer does not support RC4, which is the selected mode | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| unsupported_encryption_mode | 73 | The peer does not support any of the encryption modes that the | +| | | client supports | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| unsupported_encryption_mode_selected | 74 | The peer selected an encryption mode that the client did not | +| | | advertise and does not support | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_pad_size | 75 | The pad size used in the encryption handshake is of invalid size | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_encrypt_handshake | 76 | The encryption handshake is invalid | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| no_incoming_encrypted | 77 | The client is set to not support incoming encrypted connections | +| | | and this is an encrypted connection | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| no_incoming_regular | 78 | The client is set to not support incoming regular bittorrent | +| | | connections, and this is a regular connection | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| duplicate_peer_id | 79 | The client is already connected to this peer-ID | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_removed | 80 | Torrent was removed | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| packet_too_large | 81 | The packet size exceeded the upper sanity check-limit | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| reserved | 82 | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| http_error | 83 | The web server responded with an error | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| missing_location | 84 | The web server response is missing a location header | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_redirection | 85 | The web seed redirected to a path that no longer matches the | +| | | .torrent directory structure | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| redirecting | 86 | The connection was closed because it redirected to a different | +| | | URL | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_range | 87 | The HTTP range header is invalid | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| no_content_length | 88 | The HTTP response did not have a content length | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| banned_by_ip_filter | 89 | The IP is blocked by the IP filter | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| too_many_connections | 90 | At the connection limit | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| peer_banned | 91 | The peer is marked as banned | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| stopping_torrent | 92 | The torrent is stopping, causing the operation to fail | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| too_many_corrupt_pieces | 93 | The peer has sent too many corrupt pieces and is banned | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_not_ready | 94 | The torrent is not ready to receive peers | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| peer_not_constructed | 95 | The peer is not completely constructed yet | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| session_closing | 96 | The `session`__ is closing, causing the operation to fail | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| optimistic_disconnect | 97 | The peer was disconnected in order to leave room for a | +| | | potentially better peer | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_finished | 98 | The torrent is finished | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| no_router | 99 | No UPnP router found | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| metadata_too_large | 100 | The metadata message says the metadata exceeds the limit | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_metadata_request | 101 | The peer sent an invalid metadata request message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_metadata_size | 102 | The peer advertised an invalid metadata size | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_metadata_offset | 103 | The peer sent a message with an invalid metadata offset | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_metadata_message | 104 | The peer sent an invalid metadata message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| pex_message_too_large | 105 | The peer sent a peer exchange message that was too large | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_pex_message | 106 | The peer sent an invalid peer exchange message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_lt_tracker_message | 107 | The peer sent an invalid tracker exchange message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| too_frequent_pex | 108 | The peer sent an pex messages too often. This is a possible | +| | | attempt of and attack | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| no_metadata | 109 | 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. | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_dont_have | 110 | 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. | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| requires_ssl_connection | 111 | The peer tried to connect to an SSL torrent without connecting | +| | | over SSL. | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_ssl_cert | 112 | The peer tried to connect to a torrent with a certificate | +| | | for a different torrent. | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| not_an_ssl_torrent | 113 | the torrent is not an SSL torrent, and the operation requires | +| | | an SSL torrent | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| banned_by_port_filter | 114 | peer was banned because its listen port is within a banned port | +| | | range, as specified by the `port_filter`__. | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_session_handle | 115 | The `session_handle`__ is not referring to a valid session_impl | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_listen_socket | 116 | the listen socket associated with this request was closed | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_hash_request | 117 | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_hashes | 118 | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_hash_reject | 119 | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| deprecated_120 | 120 | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| deprecated_121 | 121 | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| deprecated_122 | 122 | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| deprecated_123 | 123 | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| deprecated_124 | 124 | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| missing_file_sizes | 130 | The resume data file is missing the ``file sizes`` `entry`__ | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| no_files_in_resume_data | 131 | The resume data file ``file sizes`` `entry`__ is empty | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| missing_pieces | 132 | The resume data file is missing the ``pieces`` and ``slots`` `entry`__ | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| mismatching_number_of_files | 133 | The number of files in the resume data does not match the number | +| | | of files in the torrent | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| mismatching_file_size | 134 | One of the files on disk has a different size than in the fast | +| | | resume file | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| mismatching_file_timestamp | 135 | One of the files on disk has a different timestamp than in the | +| | | fast resume file | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| not_a_dictionary | 136 | The resume data file is not a dictionary | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_blocks_per_piece | 137 | The ``blocks per piece`` `entry`__ is invalid in the resume data file | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| missing_slots | 138 | The resume file is missing the ``slots`` `entry`__, which is required | +| | | for torrents with compact allocation. *DEPRECATED* | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| too_many_slots | 139 | The resume file contains more slots than the torrent | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_slot_list | 140 | The ``slot`` `entry`__ is invalid in the resume data | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_piece_index | 141 | One index in the ``slot`` list is invalid | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| pieces_need_reorder | 142 | 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* | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| resume_data_not_modified | 143 | 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. | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| http_parse_error | 150 | The HTTP header was not correctly formatted | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| http_missing_location | 151 | The HTTP response was in the 300-399 range but lacked a location | +| | | header | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| http_failed_decompress | 152 | The HTTP response was encoded with gzip or deflate but | +| | | decompressing it failed | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| no_i2p_router | 160 | The URL specified an i2p address, but no i2p router is configured | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| no_i2p_endpoint | 161 | i2p acceptor is not available yet, can't announce without endpoint | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| scrape_not_available | 170 | The tracker URL doesn't support transforming it into a scrape | +| | | URL. i.e. it doesn't contain "announce. | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_tracker_response | 171 | invalid tracker response | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_peer_dict | 172 | invalid peer dictionary `entry`__. Not a dictionary | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| tracker_failure | 173 | tracker sent a failure message | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_files_entry | 174 | missing or invalid ``files`` `entry`__ | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_hash_entry | 175 | missing or invalid ``hash`` `entry`__ | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_peers_entry | 176 | missing or invalid ``peers`` and ``peers6`` `entry`__ | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_tracker_response_length | 177 | UDP tracker response packet has invalid size | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_tracker_transaction_id | 178 | invalid transaction id in UDP tracker response | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| invalid_tracker_action | 179 | invalid action field in UDP tracker response | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| announce_skipped | 180 | skipped announce (because it's assumed to be unreachable over the | +| | | given source network interface) | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| no_entropy | 200 | random number generation failed | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| ssrf_mitigation | 201 | blocked by SSRF mitigation | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| blocked_by_idna | 202 | blocked because IDNA host names are banned | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_unknown_version | 210 | the torrent file has an unknown meta version | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_missing_file_tree | 211 | the v2 torrent file has no file tree | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_missing_meta_version | 212 | the torrent contains v2 keys but does not specify meta version 2 | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_inconsistent_files | 213 | the v1 and v2 file metadata does not match | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_missing_piece_layer | 214 | one or more files are missing piece layer hashes | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_invalid_piece_layer | 215 | a piece layer has the wrong size or failed hash check | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_missing_pieces_root | 216 | a v2 file `entry`__ has no root hash | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_inconsistent_hashes | 217 | the v1 and v2 hashes do not describe the same data | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| torrent_invalid_pad_file | 218 | a file in the v2 metadata has the pad attribute set | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ +| error_code_max | 219 | the number of error codes | +| | | | ++--------------------------------------+-------+---------------------------------------------------------------------------+ + + +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#entry +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Torrent_Handle.html#torrent_handle +__ reference-Bencoding.html#entry +__ reference-Utility.html#bitfield +__ reference-Session.html#session +__ reference-Filter.html#port_filter +__ reference-Session.html#session_handle +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#entry +__ reference-Torrent_Handle.html#only_if_modified +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#entry + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +enum http_errors +---------------- + +Declared in "`libtorrent/error_code.hpp`__" + + +__ include/libtorrent/error_code.hpp + ++-----------------------+-------+-------------+ +| name | value | description | ++=======================+=======+=============+ +| 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 | | ++-----------------------+-------+-------------+ + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_storage_counters +-------------------- + +Declared in "`libtorrent/kademlia/dht_storage.hpp`__" + + +__ include/libtorrent/kademlia/dht_storage.hpp + +This structure hold the relevant `counters`__ for the storage + + +__ reference-Stats.html#counters + + +.. parsed-literal:: + + + 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; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +reset() +....... + +.. parsed-literal:: + + void **reset** (); + + +This member function set the `counters`__ to zero. + + +__ reference-Stats.html#counters + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_storage_interface +--------------------- + +Declared in "`libtorrent/kademlia/dht_storage.hpp`__" + + +__ include/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. + + +__ reference-DHT.html#dht_default_storage_constructor() + + +.. parsed-literal:: + + + struct dht_storage_interface + { + virtual void **update_node_ids** (std\:\:vector 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 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 buf + , signature const& sig + , sequence\_number seq + , public\_key const& pk + , span salt + , address const& addr) = 0; + virtual int **get_infohashes_sample** (entry& item) = 0; + virtual void **tick** () = 0; + virtual dht_storage_counters **counters** () const = 0; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +update_node_ids() +................. + +.. parsed-literal:: + + virtual void **update_node_ids** (std\:\:vector 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +get_peers() +........... + +.. parsed-literal:: + + 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 `settings_pack::dht_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. + + +__ reference-Settings.html#dht_max_peers_reply + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +announce_peer() +............... + +.. parsed-literal:: + + 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. + + +__ reference-Bencoding.html#entry + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +get_immutable_item() +.................... + +.. parsed-literal:: + + 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. + + +__ reference-Bencoding.html#entry +__ reference-Bencoding.html#entry + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +put_immutable_item() +.................... + +.. parsed-literal:: + + virtual void **put_immutable_item** (sha1\_hash const& target + , span 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 +`settings_pack::dht_max_dht_items`__. + + + +__ reference-Settings.html#dht_max_dht_items + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +get_mutable_item_seq() +...................... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +get_mutable_item() +.................. + +.. parsed-literal:: + + 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. + + +__ reference-Bencoding.html#entry + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +put_mutable_item() +.................. + +.. parsed-literal:: + + 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; + + +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 +`settings_pack::dht_max_dht_items`__. + + + +__ reference-Settings.html#dht_max_dht_items + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +get_infohashes_sample() +....................... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +tick() +...... + +.. parsed-literal:: + + 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +counters() +.......... + +.. parsed-literal:: + + virtual dht_storage_counters **counters** () const = 0; + + +return stats `counters`__ for the store + + +__ reference-Stats.html#counters + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_state +--------- + +Declared in "`libtorrent/kademlia/dht_state.hpp`__" + + +__ include/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`_ + +.. _`BEP 32`: https://www.bittorrent.org/beps/bep_0032.html + + + + +.. parsed-literal:: + + + struct dht_state + { + void **clear** (); + + node_ids_t nids; + std::vector nodes; + std::vector nodes6; + }; + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +nodes + the bootstrap nodes saved from the buckets node + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + +nodes6 + the bootstrap nodes saved from the IPv6 buckets node + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +dht_default_storage_constructor() +--------------------------------- + +Declared in "`libtorrent/kademlia/dht_storage.hpp`__" + + +__ include/libtorrent/kademlia/dht_storage.hpp + +.. parsed-literal:: + + std::unique_ptr **dht_default_storage_constructor** ( + settings\_interface 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +sign_mutable_item() +------------------- + +Declared in "`libtorrent/kademlia/item.hpp`__" + + +__ include/libtorrent/kademlia/item.hpp + +.. parsed-literal:: + + signature **sign_mutable_item** ( + span v + , span 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +announce_flags_t +---------------- + +Declared in "`libtorrent/kademlia/announce_flags.hpp`__" + + +__ include/libtorrent/kademlia/announce_flags.hpp + +.. raw:: html + + + +seed + announce to DHT as a seed + + + +.. raw:: html + + + +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. + + + +.. raw:: html + + + +ssl_torrent + Specify the port number for the SSL listen socket in the DHT announce. + + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + + +.. raw:: html + + [report issue] + + + +write_resume_data_buf() write_resume_data() +------------------------------------------- + +Declared in "`libtorrent/write_resume_data.hpp`__" + + +__ include/libtorrent/write_resume_data.hpp + +.. parsed-literal:: + + entry **write_resume_data** (add\_torrent\_params const& atp); + std::vector **write_resume_data_buf** (add\_torrent\_params const& atp); + + +this function turns the resume data in an ``add_torrent_params`` object +into a bencoded structure + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +write_torrent_file() +-------------------- + +Declared in "`libtorrent/write_resume_data.hpp`__" + + +__ include/libtorrent/write_resume_data.hpp + +.. parsed-literal:: + + entry **write_torrent_file** (add\_torrent\_params const& atp); + + +writes only the fields to create a .torrent file. This function may fail +with a ``std::system_error`` exception if: + +* The `add_torrent_params`__ object passed to this function does not contain the + info dictionary (the ``ti`` field) +* The piece layers are not complete for all files that need them + + +__ reference-Add_Torrent.html#add_torrent_params + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +read_resume_data() +------------------ + +Declared in "`libtorrent/read_resume_data.hpp`__" + + +__ include/libtorrent/read_resume_data.hpp + +.. parsed-literal:: + + add_torrent_params **read_resume_data** (bdecode\_node const& rd + , int piece\_limit = 0x200000); + add_torrent_params **read_resume_data** (bdecode\_node const& rd + , error\_code& ec, int piece\_limit = 0x200000); + add_torrent_params **read_resume_data** (span buffer + , load\_torrent\_limits const& cfg = {}); + add_torrent_params **read_resume_data** (span buffer + , error\_code& ec, load\_torrent\_limits const& cfg = {}); + + +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. + +The ``piece_limit`` parameter determines the largest number of pieces +allowed in the torrent that may be loaded as part of the resume data, if +it contains an ``info`` field. The overloads that take a flat buffer are +instead configured with limits on torrent sizes via load_torrent limits. + +In order to support large torrents, it may also be necessary to raise the +`settings_pack::max_piece_count`__ setting and pass a higher limit to calls +to `torrent_info::parse_info_section()`__. + + +__ reference-Add_Torrent.html#add_torrent_params +__ reference-Torrent_Info.html#torrent_info +__ reference-Settings.html#max_piece_count +__ reference-Torrent_Info.html#parse_info_section() + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +ed25519_create_seed() +--------------------- + +Declared in "`libtorrent/kademlia/ed25519.hpp`__" + + +__ include/libtorrent/kademlia/ed25519.hpp + +.. parsed-literal:: + + std::array **ed25519_create_seed** (); + + +See documentation of internal random_bytes + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +ed25519_create_keypair() +------------------------ + +Declared in "`libtorrent/kademlia/ed25519.hpp`__" + + +__ include/libtorrent/kademlia/ed25519.hpp + +.. parsed-literal:: + + std::tuple **ed25519_create_keypair** ( + std\:\:array 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. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +ed25519_sign() +-------------- + +Declared in "`libtorrent/kademlia/ed25519.hpp`__" + + +__ include/libtorrent/kademlia/ed25519.hpp + +.. parsed-literal:: + + signature **ed25519_sign** (span msg + , public\_key const& pk, secret\_key const& sk); + + +Creates a signature of the given message with the given key pair. + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +ed25519_verify() +---------------- + +Declared in "`libtorrent/kademlia/ed25519.hpp`__" + + +__ include/libtorrent/kademlia/ed25519.hpp + +.. parsed-literal:: + + bool **ed25519_verify** (signature const& sig + , span msg, public\_key const& pk); + + +Verifies the signature on the given message using ``pk`` + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +ed25519_add_scalar() +-------------------- + +Declared in "`libtorrent/kademlia/ed25519.hpp`__" + + +__ include/libtorrent/kademlia/ed25519.hpp + +.. parsed-literal:: + + public_key **ed25519_add_scalar** (public\_key const& pk + , std\:\:array const& scalar); + secret_key **ed25519_add_scalar** (secret\_key const& sk + , std\:\:array 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 + + + +.. raw:: html + + + +.. raw:: html + + [report issue] + + + +ed25519_key_exchange() +---------------------- + +Declared in "`libtorrent/kademlia/ed25519.hpp`__" + + +__ include/libtorrent/kademlia/ed25519.hpp + +.. parsed-literal:: + + std::array **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 + + + + + +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _extensions: manual-ref.html#extensions +.. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html +.. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`rate based choking`: manual-ref.html#rate-based-choking diff --git a/docs/streaming.rst b/docs/streaming.rst new file mode 100644 index 0000000..254f766 --- /dev/null +++ b/docs/streaming.rst @@ -0,0 +1,147 @@ +Streaming implementation +======================== + +.. include:: header.rst + +This documents describes the algorithm libtorrent uses to satisfy time critical +piece requests, i.e. streaming. + +streaming vs sequential_download +-------------------------------- + +Libtorrent's ``sequential_download`` mode and the time-critical logic can be +understood as two different ways of managing *peer request queues*. + +``sequential_download`` will simply wait until a queue slot opens up, and +request the next piece in the sequence. This mechanism is even simpler than the +classic "rarest-first" algorithm; it does a good job of keeping request queues +full, thus saturating available download bandwidth; and pieces do arrive +*roughly* in-order. However, it's sub-optimal for streaming: piece 0 may be +requested from a slow peer, and fast peers will get requests for later-index +pieces instead of retrying more-critical ones. + +The time-critical logic does more *active management* of peer request queues, +such that the most time-critical pieces occupy the "best" queue slots, across +all peers. It can be considered an advanced version of ``sequential_download``. +The main trade-off is that it is more complex to implement and utilize. + +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..7739455 --- /dev/null +++ b/docs/style.css @@ -0,0 +1,426 @@ +@media (prefers-color-scheme: light) { + body { + background-color: white; + color: black; + } +} + +@media (prefers-color-scheme: dark) { + body { + background-color: black; + color: #ddd; + } +} + +/* STYLE */ + +body { + margin: 0px; + font-size: 12pt; +} + +dt { + margin-bottom: 0.3em; + font-style: italic; + font-weight: 600; +} + +dd { + margin-left: 2em; + margin-bottom: 1em; +} + +tt { + font-family: monospace; +} + +h1 { font-size: 1.7em; } +h2 { font-size: 1.5em; } + +@media (prefers-color-scheme: light) { +tt { background-color: rgba(0, 0, 0, 0.05); } +table { border-color: #ccc; } +th, td { border-color: #ddd; } +th { border-bottom-color: black; } +a.reference, a { color: #000070; } +hr { border-color: #eee; } +} +@media (prefers-color-scheme: dark) { +tt { background-color: rgba(255, 255, 255, 0.2); } +table { border-color: #777; } +th, td { border-color: #444; } +th { border-bottom-color: white; } +a.reference, a { color: #bbf; } +hr { border-color: #777; } +} + +hr { + border-bottom-width: 1px; + border-style: solid; +} + +table { + margin-bottom: 1em; + border-collapse: collapse; + border-style: solid; +} + +th, td { padding: 0.2em; } + +th { + border-bottom-style: solid; + border-bottom-width: 1px; +} + +a { + text-decoration: none; +} + +a:hover +{ + text-decoration: underline; +} + +p.last { + margin-bottom: 0.3em; +} + +p.first { + margin-top: 0.3em; +} + +.align-right { + float: right; +} + +@media (prefers-color-scheme: dark) { +.bw { + filter: invert(1); +} +} + +/* TEMPLATE */ + +@media (prefers-color-scheme: light) { +#footer { + color: #777; +} +#footer a { color: #555; } +#footer a:hover { color: #000; } +#gradient { background: linear-gradient(#aaa, #ddd); } +#filler { background: linear-gradient(#ddd, #fff); } +} +@media (prefers-color-scheme: dark) { +#footer { + color: #888; +} +#footer a { color: #ccc; } +#footer a:hover { color: #fff; } +#gradient { background: #444; } +#filler { background: linear-gradient(#444, black); } +} +#container { + text-align: left; + max-width: 60em; + margin: 5px auto; + position: relative; + padding: 3px; +} + +#gradient { + height: 40px; +} + +#filler { + min-height: 400px; + height: 100%; +} + +#footer { + margin-bottom: 0px; + margin-left: auto; + margin-right: auto; + column-count: 3; + column-width: 7em; + max-width: 40em; +} + +#footer a { + text-decoration: none; +} + +#footer a:hover { + text-decoration: underline; +} + +table.docinfo { + float: right; + width: 200px; + margin-right: 0px; + margin-left: 20px; + margin-bottom: 20px; + border: none; +} + +@media screen and (max-width: 499px) { +table.docinfo { + display: none; +} +} + +table.docinfo th { + text-align: right; + background-color: transparent; + border: none; +} + +table.docinfo td { + padding-left: 10px; +} + + +/* FRONT PAGE */ + +@media screen and (min-width: 500px) { +#librarySidebar { + float: left; + width: 13em; +} +#libraryBody { + margin-left: 13em; +} +} + +@media screen and (max-width: 240px) { +#librarySidebar ul { + list-style-type: none; + padding-inline-start: 0px; +} +} + +#librarySidebar li { padding-bottom: 0.35em; } + +@media (prefers-color-scheme: light) { +#libraryBody { + border-color: #eee; +} +} +@media (prefers-color-scheme: dark) { +#libraryBody { + border-color: #777; +} +} + +#libraryBody { + border-left-style: solid; + border-left-width: 1px; + padding-left: 10px; + margin-right: 10px; +} + +.screenshot { + width: 100%; +} + +.front-page-screenshot { + float: right; +} +@media screen and (max-width: 890px) { +.front-page-screenshot { + display: none; +} +} + +.front-page-qr { + float: right; + clear: right; +} +@media screen and (max-width: 600px) { +.front-page-qr { + display: none; +} +} + +.report-issue { + float: right; + font-size: 90%; +} + +/* REFERENCE MAIN TABLE OF CONTENT */ + +@media (prefers-color-scheme: light) { +div.main-toc { border-color: #999; } +} +@media (prefers-color-scheme: dark) { +div.main-toc { border-color: #888; } +} +div.main-toc { + column-count: 4; + column-width: 13em; + border-style: solid; + border-width: 1px; + padding: 5px; + margin-bottom: 10px; +} + +.rubric +{ + margin-top: 5px; + margin-bottom: 5px; + font-size: 120%; + font-weight: bold; +} + + +/* TABLE OF CONTENT */ + +@media (prefers-color-scheme: light) { +#table-of-contents { + background-color: white; + border-color: #a1c5d6; +} +} +@media (prefers-color-scheme: dark) { +#table-of-contents { + background-color: black; + border-color: #76c; +} +} +#table-of-contents { + margin-left: 20px; + padding: 0.8em; + border-style: solid; + border-width: 1px; + position: relative; + z-index: 1; +} + +@media screen and (min-width: 500px) { +#table-of-contents { + width: 15em; + float: right; + clear: right; +} +} + +#table-of-contents p { + font-size: 140%; + font-weight: bold; + padding-bottom: 0.5em; + margin: 0; +} + +#table-of-contents ul { + margin: 0; + padding: 0 0 0 0.8em; + list-style: square; + text-align: left; + line-height: 1.5em; +} + +@media screen and (max-width: 319px) { +#table-of-contents ul { + list-style-type: none; + padding-inline-start: 0px; +} +} + +#table-of-contents a.reference { + border: none; + font-weight: bold; +} + +#table-of-contents li li a.reference { + font-weight: normal; + padding: 0; +} + + +/* CODE BLOCKS */ + +@media (prefers-color-scheme: light) { +pre { + background: #f6f6f6; + border-color: #bbb; +} +} +@media (prefers-color-scheme: dark) { +pre { + background: #222; + border-color: #666; +} +} +pre { + font-family: monospace; + padding: 5px 10px 5px 10px; + border-style: solid; + border-width: 1px; + margin: 1em 0; +} + + +/* SYNTAX HIGHLIGHTING */ + +.keyword { font-weight: bold } + +@media (prefers-color-scheme: light) { +.string { color: #771; } +.comment { font-style: italic; color: #559; } +.preproc { font-style: italic; color: #959; } +.number { color: #595; } +} +@media (prefers-color-scheme: dark) { +.string { color: #ff6; } +.comment { font-style: italic; color: #99f; } +.preproc { font-style: italic; color: #f7f; } +.number { color: #7f7; } +} + +/* ALERT BOXES */ + +@media (prefers-color-scheme: light) { +div.warning, div.note, div.important { + background: #f1fff5; + border-color: #d1dfd5; +} +div.warning { + background: #fffdca; + border-color: #dddd80; +} +div.note .admonition-title { border-bottom-color: #d1dfd5; } +div.warning .admonition-title { border-bottom-color: #dddd80; } +} +@media (prefers-color-scheme: dark) { +div.warning, div.note, div.important { + background: #0f3a0f; + border-color: #5d9e5d; +} +div.warning { + background: #666507; + border-color: #dbd818; +} +div.warning .admonition-title { border-bottom-color: #dbd818; } +div.note .admonition-title { border-bottom-color: #5d9e5d; } +} +div.warning, div.note, div.important { + width: 80%; + margin: 1.5em auto; + border-style: solid; + border-width: 1px; + padding: 5px 10px 5px 10px; +} + +div.warning { + border-style: solid; + border-width: 1px; +} + +p.admonition-title { + font-size: 128%; + letter-spacing: 2px; + text-transform: uppercase; + margin: 0 0 0.5em 0; + border-bottom-style: solid; + border-bottom-width: 1px; +} + diff --git a/docs/stylesheet b/docs/stylesheet new file mode 100644 index 0000000..3beb380 --- /dev/null +++ b/docs/stylesheet @@ -0,0 +1,287 @@ +{ + "embeddedFonts" : [ ], + "pageSetup" : { + "size": "A4", + "width": null, + "height": null, + "margin-top": "2cm", + "margin-bottom": "2cm", + "margin-left": "2cm", + "margin-right": "2cm", + "margin-gutter": "0cm", + "firstTemplate": "oneColumn" + }, + "pageTemplates" : { + "coverPage": { + "frames": [ + ["0cm", "0cm", "100%", "100%"] + ], + "showHeader" : false, + "showFooter" : false + }, + "oneColumn": { + "frames": [ + ["0cm", "0cm", "100%", "100%"] + ] + }, + "twoColumn": { + "frames": [ + ["0cm", "0cm", "49%", "100%"], + ["51%", "0cm", "49%", "100%"] + ] + } + }, + "fontsAlias" : { + "stdFont": "Times-Roman", + "stdBold": "Times-Bold", + "stdItalic": "Times-Italic", + "stdBoldItalic": "Times-BoldItalic", + "stdMono": "Courier", + "stdMonoItalic": "Courier-Oblique", + "stdMonoBold": "Courier-Bold", + "stdMonoBoldItalic": "Courier-BoldOblique", + "stdSerif": "Times-Roman" + }, + "linkColor" : "black", + "styles" : [ + [ "base" , { + "parent": null, + "fontName": "stdFont", + "fontSize":10, + "leading":12, + "leftIndent":0, + "rightIndent":0, + "firstLineIndent":0, + "alignment":"TA_LEFT", + "spaceBefore":0, + "spaceAfter":0, + "bulletFontName":"stdFont", + "bulletFontSize":10, + "bulletIndent":0, + "textColor": "black", + "backColor": null, + "wordWrap": null, + "borderWidth": 0, + "borderPadding": 0, + "borderColor": null, + "borderRadius": null, + "allowWidows": false, + "allowOrphans": false, + "hyphenation": false + }] , + ["normal" , { + "parent": "base" + }], + ["title-reference" , { + "parent": "normal", + "fontName": "stdItalic" + }], + ["bodytext" , { + "parent": "normal", + "spaceBefore":6, + "alignment": "TA_JUSTIFY", + "hyphenation": true + }], + ["footer" , { + "parent": "normal", + "alignment": "TA_CENTER" + }], + ["header" , { + "parent": "normal", + "alignment": "TA_CENTER" + }], + ["attribution" , { + "parent": "bodytext", + "alignment": "TA_RIGHT" + }], + ["figure" , { + "parent": "bodytext", + "alignment": "TA_CENTER" + }], + ["definition-list-term" , { + "parent": "normal", + "fontName": "stdBold", + "spaceBefore": 4, + "spaceAfter": 0, + "keepWithNext": true + }], + ["definition-list-classifier" , { + "parent": "normal", + "fontName": "stdItalic" + }], + ["definition" , { + "parent": "bodytext", + "firstLineIndent": 0, + "bulletIndent": 0, + "spaceBefore": 0 + }], + ["fieldname" , { + "parent": "bodytext", + "alignment": "TA_RIGHT", + "fontName": "stdBold" + }], + ["rubric" , { + "parent": "bodytext", + "textColor": "darkred", + "alignment": "TA_CENTER" + }], + ["italic" , { + "parent": "bodytext", + "fontName": "stdItalic" + }], + ["title" , { + "parent": "normal", + "fontName": "stdBold", + "fontSize": "200%", + "alignment": "TA_CENTER", + "spaceBefore": 12, + "spaceAfter": 10 + }], + ["subtitle" , { + "parent": "title", + "spaceBefore": 9, + "spaceAfter": 6, + "fontSize": "75%" + }], + ["heading1" , { + "parent": "normal", + "fontName": "stdBold", + "fontSize": "150%", + "keepWithNext": true, + "spaceBefore": 9, + "spaceAfter": 3 + }], + ["heading2" , { + "parent": "normal", + "fontName": "stdBold", + "fontSize": "125%", + "keepWithNext": true, + "spaceBefore": 9, + "spaceAfter": 3 + }], + ["heading3" , { + "parent": "normal", + "fontName": "stdBold", + "keepWithNext": true, + "spaceBefore": 4, + "spaceAfter": 2 + }], + ["heading4" , { + "parent": "normal", + "fontName": "stdBold", + "keepWithNext": true + }], + ["sidebar-title" , { + "parent": "heading3" + }], + ["sidebar-subtitle" , { + "parent": "heading4" + }], + ["sidebar" , { + "float": "left", + "width": "30%", + "parent": "normal", + "backColor": "beige", + "borderColor": "darkgray", + "borderPadding": 8, + "borderWidth": 0.5 + }], + ["literal" , { + "parent": "normal", + "fontName": "stdMono", + "firstLineIndent": 0 + }], + ["table" , { + "rowBackgrounds" : ["f0f0d8","#ffffe8"], + "borderColor": "white" + }], + ["table-title" , { + "parent" : "heading4", + "backColor" : "#e0e0c8", + "alignment" : "TA_CENTER" + }], + ["table-heading" , { + "parent" : "heading4", + "backColor" : "#e0e0c0", + "alignment" : "TA_CENTER", + "valign" : "BOTTOM" + }], + ["code" , { + "parent": "literal", + "fontSize": "75%", + "leftIndent": 0, + "spaceBefore": 5, + "spaceAfter": 5, + "backColor": "#e7e7e7", + "borderColor": "#808080", + "borderRadius": 0, + "borderWidth": 0.5, + "borderPadding": 4 + }], + ["pygments-n" , {"parent": "code"}], + ["pygments-nx" , {"parent": "code"}], + ["pygments-p" , {"parent": "code"}], + ["pygments-hll", {"parent": "code", "backColor": "#ffffcc"}], + ["pygments-c", {"textColor": "#008800", "parent": "code"}], + ["pygments-err", {"parent": "code"}], + ["pygments-k", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-o", {"textColor": "#666666", "parent": "code"}], + ["pygments-cm", {"textColor": "#008800", "parent": "code"}], + ["pygments-cp", {"textColor": "#008800", "parent": "code"}], + ["pygments-c1", {"textColor": "#008800", "parent": "code"}], + ["pygments-cs", {"textColor": "#008800", "parent": "code"}], + ["pygments-gd", {"textColor": "#A00000", "parent": "code"}], + ["pygments-ge", {"parent": "code"}], + ["pygments-gr", {"textColor": "#FF0000", "parent": "code"}], + ["pygments-gh", {"textColor": "#000080", "parent": "code"}], + ["pygments-gi", {"textColor": "#00A000", "parent": "code"}], + ["pygments-go", {"textColor": "#808080", "parent": "code"}], + ["pygments-gp", {"textColor": "#000080", "parent": "code"}], + ["pygments-gs", {"parent": "code"}], + ["pygments-gu", {"textColor": "#800080", "parent": "code"}], + ["pygments-gt", {"textColor": "#0040D0", "parent": "code"}], + ["pygments-kc", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-kd", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-kn", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-kp", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-kr", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-kt", {"textColor": "#00BB00", "parent": "code"}], + ["pygments-m", {"textColor": "#666666", "parent": "code"}], + ["pygments-s", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-na", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-nb", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-nc", {"textColor": "#0000FF", "parent": "code"}], + ["pygments-no", {"textColor": "#880000", "parent": "code"}], + ["pygments-nd", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-ni", {"textColor": "#999999", "parent": "code"}], + ["pygments-ne", {"textColor": "#D2413A", "parent": "code"}], + ["pygments-nf", {"textColor": "#00A000", "parent": "code"}], + ["pygments-nl", {"textColor": "#A0A000", "parent": "code"}], + ["pygments-nn", {"textColor": "#0000FF", "parent": "code"}], + ["pygments-nt", {"textColor": "#008000", "parent": "code"}], + ["pygments-nv", {"textColor": "#B8860B", "parent": "code"}], + ["pygments-ow", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-w", {"textColor": "#bbbbbb", "parent": "code"}], + ["pygments-mf", {"textColor": "#666666", "parent": "code"}], + ["pygments-mh", {"textColor": "#666666", "parent": "code"}], + ["pygments-mi", {"textColor": "#666666", "parent": "code"}], + ["pygments-mo", {"textColor": "#666666", "parent": "code"}], + ["pygments-sb", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-sc", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-sd", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-s2", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-se", {"textColor": "#BB6622", "parent": "code"}], + ["pygments-sh", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-si", {"textColor": "#BB6688", "parent": "code"}], + ["pygments-sx", {"textColor": "#008000", "parent": "code"}], + ["pygments-sr", {"textColor": "#BB6688", "parent": "code"}], + ["pygments-s1", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-ss", {"textColor": "#B8860B", "parent": "code"}], + ["pygments-bp", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-vc", {"textColor": "#B8860B", "parent": "code"}], + ["pygments-vg", {"textColor": "#B8860B", "parent": "code"}], + ["pygments-vi", {"textColor": "#B8860B", "parent": "code"}], + ["pygments-il", {"textColor": "#666666", "parent": "code"}] + ] +} + diff --git a/docs/template.txt b/docs/template.txt new file mode 100644 index 0000000..6d56ba4 --- /dev/null +++ b/docs/template.txt @@ -0,0 +1,42 @@ + +%(head_prefix)s +%(head)s + + + + +%(stylesheet)s +%(body_prefix)s +
    + + libtorrent logo + +
    +%(body_pre_docinfo)s +%(docinfo)s +%(body)s + +
    +
    +
    + + +%(body_suffix)s diff --git a/docs/template2.txt b/docs/template2.txt new file mode 100644 index 0000000..6fa8146 --- /dev/null +++ b/docs/template2.txt @@ -0,0 +1,45 @@ + +%(head_prefix)s +%(head)s + + + + +%(stylesheet)s +%(body_prefix)s +
    + +
    +%(body_pre_docinfo)s +%(docinfo)s +
    + +
    +%(body)s + +
    +
    +
    + + +%(body_suffix)s diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst new file mode 100644 index 0000000..a15fe3c --- /dev/null +++ b/docs/troubleshooting.rst @@ -0,0 +1,20 @@ +================= +libtorrent manual +================= + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +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``. + +.. _`mailing list`: https://lists.sourceforge.net/lists/listinfo/libtorrent-discuss + +.. image:: img/troubleshooting_thumb.png + :target: img/troubleshooting.png + diff --git a/docs/troubleshooting_thumb.png b/docs/troubleshooting_thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..9858aa6cf88f693e583d6b349228679cdbf9555f GIT binary patch literal 45948 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 VU_JGWg}C5 alerts; + ses.pop_alerts(&alerts); + + for (auto* a : alerts) { + if (lt::alert_cast(a) + || lt::alert_cast(a)) + { + std::cout << a->message() << "\n"; + } + + // ... + } + +The alerts with data will have the type session_stats_alert and there is one +session_stats_header_alert that will be posted on startup containing the column names +for all metrics. Logging this line will greatly simplify interpreting the output, +and is required for the script to work out-of-the-box. + +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 bytes of 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 bytes of 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. + +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. Some of +these macros may affect ABI (although they are not intended to). + +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 shave off some size from the executable. + +For all available options, see the `building libtorrent`_ section. Look +specifically for the ``TORRENT_DISABLE_*`` macros. + +.. _`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 cache for open file handles. 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. + +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_slots_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. + +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 and/or SHA-256. In +order to enable computing hashes in parallel, on multi-core systems, set +settings_pack::aio_threads to the number of threads libtorrent should perform +I/O and settings_pack::hashing_threads to the number of threads to compute piece +hashes in. + +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. + +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.rst b/docs/tutorial.rst new file mode 100644 index 0000000..9633d4a --- /dev/null +++ b/docs/tutorial.rst @@ -0,0 +1,305 @@ +.. 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_category::error + | lt::alert_category::storage + | lt::alert_category::status); + + 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 asynchronously, one can call +session::abort(). + +This call returns a session_proxy object, which is a handle keeping the session +state alive while shutting it down. 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: */ + +session state +------------- + +On construction, a session object is configured by a session_params object. The +session_params object notably contain session_settings, the state of the DHT +node (e.g. routing table), the session's IP filter as well as the disk I/O +back-end and dht storage to use. + +There are functions to serialize and de-serialize the session_params object to +help in restoring session state from last run. Doing so is especially helpful +for bootstrapping the DHT, using nodes from last run. + +Before destructing the session object, call ``session::session_state()`` to get +the current state as a session_params object. + +Call write_session_params() or write_session_params_buf() to serialize the state +into a bencoded entry or to a flat buffer (``std::vector``) respectively. + +On startup, before constructing the session object, load the buffer back from +disk and call read_session_params() to de-serialize it back into a session_params +object. Before passing it into the session constructor is your chance to set +update the settings_pack (``params``) member of settings_params, or configuring +the disk_io_constructor. + +example +------- + +Another updated version of the above example with the following updates: + +1. load and save session_params to file ".session" +2. allow shutting down on ``SIGINT`` + +.. include:: ../examples/bt-get3.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. + +.. _`blog post`: https://blog.libtorrent.org/2015/03/bdecode-parsers/ + 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.rst b/docs/upgrade_to_1.2.rst new file mode 100644 index 0000000..0f89495 --- /dev/null +++ b/docs/upgrade_to_1.2.rst @@ -0,0 +1,164 @@ +=========================== +Upgrading to libtorrent 1.2 +=========================== + +:Author: Arvid Norberg, arvid@libtorrent.org + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +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 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 ```` 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 ```` 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/upgrade_to_2.0.rst b/docs/upgrade_to_2.0.rst new file mode 100644 index 0000000..e1554a5 --- /dev/null +++ b/docs/upgrade_to_2.0.rst @@ -0,0 +1,336 @@ +=========================== +Upgrading to libtorrent 2.0 +=========================== + +:Author: Arvid Norberg, arvid@libtorrent.org + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +In libtorrent 2.0, some parts of the API has changed and some deprecated parts +have been removed. +This document summarizes the changes affecting library clients. + +C++11 no longer supported +========================= + +libtorrent 2.0 requires at least C++-14. To build with boost build, specify the +C++ version using the ``cxxstd=14`` build feature (14 is the default). + +boost version +============= + +The oldest boost version supported is 1.67 + +BitTorrent v2 support +===================== + +Supporting bittorrent v2 come with some changes to the API. Specifically to +support *hybrid* torrents. i.e. torrents that are compatible with v1-only +bittorrent clients as well as supporting v2 features among the peers that +support them. + +Example torrents +---------------- + +* `bittorrent-v2-hybrid-test.torrent`_ +* `bittorrent-v2-test.torrent`_ + +.. _`bittorrent-v2-hybrid-test.torrent`: https://libtorrent.org/bittorrent-v2-hybrid-test.torrent +.. _`bittorrent-v2-test.torrent`: https://libtorrent.org/bittorrent-v2-test.torrent + +info-hashes +----------- + +With bittorrent v2 support, each torrent may now have two separate info hashes, +one SHA-1 hash and one SHA-256 hash. These are bundled in a new type called +info_hash_t. Many places that previously took an info-hash as sha1_hash now +takes an info_hash_t. For backwards compatibility, info_hash_t is implicitly +convertible to and from sha1_hash and is interpreted as the v1 info-hash. +The implicit conversion is deprecated though. + +Perhaps most noteworthy is that ``add_torrent_params::info_hash`` is now +deprecated in favor of ``add_torrent_params::info_hashes`` which is an +info_hash_t. + +The alerts torrent_removed_alert, torrent_deleted_alert, +torrent_delete_failed_alert all have ``info_hash`` members. Those members are +now deprecated in favor of an ``info_hashes`` member, which is of type +info_hash_t. + +An info_hash_t object for a hybrid torrent will have both the v1 and v2 hashes +set, it will compare false to a sha1_hash of *just* the v1 hash. + +Calls to torrent_handle::info_hash() may need to be replaced by +torrent_handle::info_hashes(), in order to get both v1 and v2 hashes. + +announce_entry/tracker changes +------------------------------ + +On major change in the API is reporting of trackers. Since hybrid torrents +announce once per info-hash (once for v1 and once for v2), the tracker results +are also reported per *bittorrent version*. + +Each tracker (announce_entry) has a list of ``endpoints``. Each corresponding to +a local listen socket. Each listen socket is announced independently. The +announce_endpoint in turn has an array ``info_hashes``, containing objects of +type announce_infohash, for each bittorrent version. The array is indexed by +the enum protocol_version. There are two members, ``V1`` and ``V2``. + +Example: + +.. code:: c++ + + std::vector tr = h.trackers(); + for (lt::announce_entry const& ae : h.trackers()) { + for (lt::announce_endpoint const& aep : ae.endpoints) { + int version = 1; + for (lt::announce_infohash const& ai : aep.info_hashes) { + std::cout << "[V" << version << "] " << ae.tier << " " << ae.url + << " " << (ih.updating ? "updating" : "") + << " " << (ih.start_sent ? "start-sent" : "") + << " fails: " << ih.fails + << " msg: " << ih.message + << "\n"; + ++version; + } + } + } + +Merkle tree support removed +--------------------------- + +The old merkle tree torrent support has been removed, as BitTorrent v2 has +better support for merkle trees, where each file has its own merkle tree. + +This means add_torrent_params no longer has the ``merkle_tree`` member. Instead +it has the new ``verified_leaf_hashes`` and ``merkle_trees`` members. + +It also means the ``merkle`` flag for create_torrent has been removed. +torrent_info no longer has ``set_merkle_tree()`` and ``merkle_tree()`` member +functions. + +create_torrent changes +---------------------- + +The create_torrent class creates *hybrid* torrents by default. i.e. torrents +compatible with both v1 and v2 bittorrent clients. + +To create v1-only torrents use the ``v1_only`` flag. To create v2-only torrents, +use the ``v2_only`` flag. + +Perhaps the most important addition for v2 torrents is the new member function +set_hash2(), which is similar to set_hash(), but for the v2-part of a torrent. +One important difference is that v2 hashes are SHA-256 hashes, and they are set +*per file*. In v2 torrents, each file forms a merkle tree and each v2 piece hash +is the SHA-256 merkle root hash of the 16 kiB blocks in that piece. + +All v2 torrents have pieces aligned to files, so the ``optimize_alignment`` flag +is no longer relevant (as it's effectively always on). Similarly, the +``mutable_torrent_support`` flag is also always on. + +``pad_file_limit`` and ``alignment`` parameters to the create_torrent constructor +have also been removed. The rules for padding and alignment is well defined for +v2 torrents. + +set_file_hash() and file_hash() functions are obsolete, as v2 torrents have +a file_root() for each file. + + +on_unknown_torrent() plugin API +------------------------------- + +Since hybrid torrents have two info-hashes, the on_unknown_torrent() function +on the plugin class now takes an info_hash_t instead of a sha1_hash. + +socket_type_t +------------- + +There is a new ``enum class`` called ``socket_type_t`` used to identify different +kinds of sockets. In previous versions of libtorrent this was exposed as plain +``int`` with subtly different sets of meanings. + +Previously there was an enum value ``udp``, which has been deprecated in favor of ``utp``. + +The socket type is exposed in the following alerts, which now use the ``socket_type_t`` +enum instead of ``int``: + +* ``peer_connect_alert`` +* ``peer_disconnected_alert`` +* ``incoming_connection_alert`` +* ``listen_failed_alert`` +* ``listen_succeeded_alert`` + + +DHT settings +============ + +DHT configuration options have previously been set separately from the main client settings. +In libtorrent 2.0 they have been unified into the main settings_pack. + +Hence, `lt::dht::dht_settings` is now deprecated, in favor of the new `dht_*` +settings in settings_pack. + +Deprecating `dht_settings` also causes an API change to the dht custom storage +constructor (see session_params). Instead of taking a `dht_settings` object, it +is now passed the full `settings_pack`. This is considered a niche interface, +so there is no backward compatibility option provided. + +stats_alert +=========== + +The stats_alert is deprecated. Instead, call session::post_torrent_updates(). +This will post a state_update_alert containing torrent_status of all torrents +that have any updates since last time this function was called. + +The new mechanism scales a lot better. + +saving and restoring session state +================================== + +The functions ``save_state()`` and ``load_state()`` on the session object have +been deprecated in favor loading the session state up-front using +read_session_params() and construct the session from it. + +The session state can be acquired, in the form of a session_params object, by +calling session::session_state(). + +The session_params object is passed to the session constructor, and will restore +the state from a previous session. + +Use read_session_params() and write_session_params() to serialize and de-serialize +the session_params object. + +As a result of this, plugins that wish to save and restore state or settings +must now use the new overload of load_state(), that takes a +``std::map``. Similarly, for saving state, it now has +to be saved to a ``std::map`` via the new overload of +save_state(). + +A lot of session constructors have been deprecated in favor of the ones that take +a session_params object. The session_params object can be implicitly constructed +from a settings_pack, to cover one of the now-deprecated constructors. However, +to access this conversion `libtorrent/session_params.hpp` must be included. + +userdata is no longer a void\* +============================== + +The ``userdata`` field in add_torrent_params is no longer a raw void pointer. +Instead it is a type-safe client_data_t object. client_data_t is similar to +``std::any``, it can hold a pointer of any type by assignment and can be cast +back to that pointer via ``static_cast`` (explicit conversion). However, if the +pointer type it is cast to is not identical to what was assigned, a ``nullptr`` +is returned. Note that the type has to be identical in CV-qualifiers as well. + +This userdata field affects the plugin APIs that has this field passed into it. + +Additionally, there's now a way to ask a torrent_handle for the userdata, so it is +associated with the torrent itself. + +Adding torrents by URL no longer supported +========================================== + +The URL covers 3 separate features, all deprecated in the previous version and +removed in 2.0. + +downloading over HTTP +--------------------- + +One used to be able to add a torrent by specifying an HTTP URL in the +``add_torrent_params::url`` member. Libtorrent would download the file and attempt +to load the file as a .torrent file. The torrent_handle in this mode would +not represent a torrent, but a *potential* torrent. Its info-hash was the hash of +the URL until the torrent file could be loaded, at which point the info hash *changed*. +The corresponding ``torrent_update_alert`` has also been removed. In libtorrent 2.0 +info-hashes cannot change. (Although they can be amended with bittorrent v1 or v2 +info-hashes). + +Instead of using this feature, clients should download the .torrent files +themselves, possibly spawn their own threads, before adding them to the session. + +magnet links +------------ + +The ``add_torrent_params::url`` could also be used to add torrents by magnet link. +This was also deprecated in the previous version and has been removed in +libtorrent 2.0. Instead, use parse_magnet_uri() to construct an add_torrent_params +object to add to the session. This also allows the client to alter settings, +such as ``save_path``, before adding the magnet link. + +async loading of .torrent files +------------------------------- + +The ``add_torrent_params::url`` field also supported ``file://`` URLs. This would +use a libtorrent thread to load the file from disk, asynchronously (in the case +of async_add_torrent()). This feature has been removed. Clients should instead +load their torrents from disk themselves, before adding them to the session. +Possibly spawning their own threads. + +Disk I/O overhaul +================= + +In libtorrent 2.0, the disk I/O subsystem underwent a significant update. In +previous versions of libtorrent, each torrent has had its own, isolated, +disk storage object. This was a customization point. In order to share things +like a pool of open file handles across torrents (to have a global limit on +open file descriptors) all storage objects would share a file_pool object +passed in to them. + +In libtorrent 2.0, the default disk I/O uses memory mapped files, which means +a lot more of what used to belong in the disk caching subsystem is now handled +by the kernel. This greatly simplifies the disk code and also has the potential +of making a lot more efficient use of modern disks as well as physical memory. + +In this new system, the customization point is the whole disk I/O subsystem. +Instead of configuring a custom storage (implementing ``storage_interface``) when +adding a torrent, you can now configure a disk subsystem (implementing +disk_interface) when creating a session. + +Systems that don't support memory mapped files can still be used with a simple +``fopen()``/``fclose()`` family of functions. This disk subsystem is also not threaded +and generally more primitive than the memory mapped file one. + +Clients that need to customize storage should implement the disk_interface and +configure it at session creation time instead of ``storage_interface`` configured +in add_torrent_params. add_torrent_params no longer has a storage_constructor +member. + +As a consequence of this, ``get_storage_impl()`` has been removed from torrent_handle. + +``aio_threads`` and ``hashing_threads`` +--------------------------------------- + +In previous versions of libtorrent, the number of disk threads to use were +configured by settings_pack::aio_threads. Every fourth thread was dedicated to +run hash jobs, i.e. computing SHA-1 piece hashes to compare them against the +expected hash. + +This setting has now been split up to allow controlling the number of dedicated +hash threads independently from the number of generic disk I/O threads. +settings_pack::hashing_threads is now used to control the number of threads +dedicated to computing hashes. + +cache_size +---------- + +The ``cache_size`` setting is no longer used. The caching of disk I/O is handled +by the operating system. + +get_cache_info() get_cache_status() +----------------------------------- + +Since libtorrent no longer manages the disk cache (except for a store-buffer), +``get_cache_info()`` and ``get_cache_status()`` on the session object has also +been removed. They cannot return anything useful. + +last remnants of RSS support removed +==================================== + +The ``rss_notification`` alert category flag has been removed, which has been unused +and deprecated since libtorrent 1.2. + +The ``uuid`` member of add_torrent_params has been removed. Torrents can no longer +be added under a specific UUID. This feature was specifically meant for RSS feeds, +which was removed in the previous version of libtorrent. + diff --git a/docs/utp.rst b/docs/utp.rst new file mode 100644 index 0000000..07d555c --- /dev/null +++ b/docs/utp.rst @@ -0,0 +1,345 @@ +.. 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:: img/cwnd_thumb.png + :target: cwnd.png + :class: bw + :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:: img/delays_thumb.png + :target: delays.png + :class: bw + :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:: img/our_delay_base_thumb.png + :target: our_delay_base.png + :class: bw + :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/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..387cc6f --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,27 @@ +project(libtorrent-examples) + +set(single_file_examples + simple_client + custom_storage + stats_counters + dump_torrent + dump_bdecode + make_torrent + connection_tester + upnp_test) + +if(CMAKE_CXX_COMPILER_ID MATCHES Clang) + add_compile_options(-Wno-implicit-int-float-conversion) +endif() + +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..a7d3888 --- /dev/null +++ b/examples/Jamfile @@ -0,0 +1,60 @@ +import modules ; + +BOOST_ROOT = [ modules.peek : BOOST_ROOT ] ; + +use-project /torrent : .. ; + +if $(BOOST_ROOT) +{ + use-project /boost : $(BOOST_ROOT) ; +} + +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 + clang:-Wno-implicit-int-float-conversion + @warnings + : default-build + static + 14 + 64 + ; + +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 bt-get3 : bt-get3.cpp ; +exe stats_counters : stats_counters.cpp ; +exe dump_torrent : dump_torrent.cpp ; +exe torrent2magnet : torrent2magnet.cpp ; +exe dump_bdecode : dump_bdecode.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 ; +explicit stage_dependencies ; + +install stage : client_test connection_tester torrent2magnet make_torrent dump_torrent upnp_test stats_counters bt-get bt-get2 simple_client dump_bdecode custom_storage : . ; +install stage_client_test : client_test : . ; +install stage_connection_tester : connection_tester : . ; + +install stage_dependencies + : /torrent//torrent + : dependencies + on + SHARED_LIB + ; + 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/bt-get.cpp b/examples/bt-get.cpp new file mode 100644 index 0000000..a61d788 --- /dev/null +++ b/examples/bt-get.cpp @@ -0,0 +1,82 @@ +/* + +Copyright (c) 2016-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 +#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..b68c5fb --- /dev/null +++ b/examples/bt-get2.cpp @@ -0,0 +1,179 @@ +/* + +Copyright (c) 2016-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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using clk = std::chrono::steady_clock; + +// return the name of a torrent status enum +char const* state(lt::torrent_status::state_t s) +{ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + 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::checking_resume_data: return "checking resume"; + default: return "<>"; + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +} + +} // anonymous namespace + +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 magnet = lt::parse_magnet_uri(argv[1]); + if (buf.size()) { + lt::add_torrent_params atp = lt::read_resume_data(buf); + if (atp.info_hashes == magnet.info_hashes) magnet = std::move(atp); + } + magnet.save_path = "."; // save in current dir + ses.async_add_torrent(std::move(magnet)); + + // this is the handle we'll set once we get the notification of it being + // added + lt::torrent_handle h; + + // set when we're exiting + bool done = false; + 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(lt::torrent_handle::save_info_dict); + done = true; + } + if (lt::alert_cast(a)) { + std::cout << a->message() << std::endl; + done = true; + h.save_resume_data(lt::torrent_handle::save_info_dict); + } + + // 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(), int(b.size())); + if (done) goto done; + } + + if (lt::alert_cast(a)) { + if (done) goto done; + } + + 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 (" + << s.num_peers << " peers)\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(lt::torrent_handle::save_info_dict); + last_save_resume = clk::now(); + } + } + +done: + std::cout << "\ndone, shutting down" << std::endl; +} +catch (std::exception& e) +{ + std::cerr << "Error: " << e.what() << std::endl; +} + diff --git a/examples/bt-get3.cpp b/examples/bt-get3.cpp new file mode 100644 index 0000000..d5796cc --- /dev/null +++ b/examples/bt-get3.cpp @@ -0,0 +1,213 @@ +/* + +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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using clk = std::chrono::steady_clock; + +// return the name of a torrent status enum +char const* state(lt::torrent_status::state_t s) +{ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + 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::checking_resume_data: return "checking resume"; + default: return "<>"; + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +} + +std::vector load_file(char const* filename) +{ + std::ifstream ifs(filename, std::ios_base::binary); + ifs.unsetf(std::ios_base::skipws); + return {std::istream_iterator(ifs), std::istream_iterator()}; +} + +// set when we're exiting +std::atomic shut_down{false}; + +void sighandler(int) { shut_down = true; } + +} // anonymous namespace + +int main(int argc, char const* argv[]) try +{ + if (argc != 2) { + std::cerr << "usage: " << argv[0] << " " << std::endl; + return 1; + } + + // load session parameters + auto session_params = load_file(".session"); + lt::session_params params = session_params.empty() + ? lt::session_params() : lt::read_session_params(session_params); + params.settings.set_int(lt::settings_pack::alert_mask + , lt::alert_category::error + | lt::alert_category::storage + | lt::alert_category::status); + + lt::session ses(params); + clk::time_point last_save_resume = clk::now(); + + // load resume data from disk and pass it in as we add the magnet link + auto buf = load_file(".resume_file"); + + lt::add_torrent_params magnet = lt::parse_magnet_uri(argv[1]); + if (buf.size()) { + lt::add_torrent_params atp = lt::read_resume_data(buf); + if (atp.info_hashes == magnet.info_hashes) magnet = std::move(atp); + } + magnet.save_path = "."; // save in current dir + ses.async_add_torrent(std::move(magnet)); + + // this is the handle we'll set once we get the notification of it being + // added + lt::torrent_handle h; + + std::signal(SIGINT, &sighandler); + + // set when we're exiting + bool done = false; + for (;;) { + std::vector alerts; + ses.pop_alerts(&alerts); + + if (shut_down) { + shut_down = false; + auto const handles = ses.get_torrents(); + if (handles.size() == 1) { + handles[0].save_resume_data(lt::torrent_handle::save_info_dict); + done = true; + } + } + + 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(lt::torrent_handle::save_info_dict); + done = true; + } + if (lt::alert_cast(a)) { + std::cout << a->message() << std::endl; + done = true; + h.save_resume_data(lt::torrent_handle::save_info_dict); + } + + // 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(), int(b.size())); + if (done) goto done; + } + + if (lt::alert_cast(a)) { + if (done) goto done; + } + + 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 (" + << s.num_peers << " peers)\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(lt::torrent_handle::save_info_dict); + last_save_resume = clk::now(); + } + } + +done: + std::cout << "\nsaving session state" << std::endl; + { + std::ofstream of(".session", std::ios_base::binary); + of.unsetf(std::ios_base::skipws); + auto const b = write_session_params_buf(ses.session_state() + , lt::save_state_flags_t::all()); + of.write(b.data(), int(b.size())); + } + + 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..2b8575e --- /dev/null +++ b/examples/client_test.cpp @@ -0,0 +1,2192 @@ +/* + +Copyright (c) 2003-2020, Arvid Norberg +Copyright (c) 2015, Mike Tzou +Copyright (c) 2016, 2018-2019, Alden Torres +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2017, AllSeeingEyeTolledEweSew +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2019, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/session_params.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 "libtorrent/disabled_disk_io.hpp" // for disabled_disk_io_constructor + +#include "torrent_view.hpp" +#include "session_view.hpp" +#include "print.hpp" + + +#ifdef _WIN32 + +#include +#include + +#else + +#include +#include +#include +#include +#include + +#endif + +namespace { + +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::total_seconds; +using lt::torrent_flags_t; +using lt::seconds; +using lt::operator "" _sv; +using lt::address_v4; +using lt::address_v6; +using lt::make_address_v6; +using lt::make_address_v4; +using lt::make_address; + +using std::chrono::duration_cast; +using std::stoi; + +#ifdef _WIN32 + +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 + +struct set_keypress +{ + enum terminal_mode { + echo = 1, + canonical = 2 + }; + + explicit set_keypress(std::uint8_t const mode = 0) + { + using ul = unsigned long; + + 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 &= ul(~ECHO); + + if (mode & canonical) new_settings.c_lflag |= ICANON; + else new_settings.c_lflag &= ul(~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); + auto const delay = total_milliseconds(done - lt::clock_type::now()); + timeval tv = {int(delay / 1000), int((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_peers_legend = 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 print_ip = true; +bool print_peaks = false; +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(), int(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("abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ", 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; + char buf[200]; + address const& addr = ep.address(); + if (addr.is_v6()) + std::snprintf(buf, sizeof(buf), "[%s]:%d", addr.to_string().c_str(), ep.port()); + else + std::snprintf(buf, sizeof(buf), "%s:%d", addr.to_string().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"; + if (print_peaks) out += " | peak "; + out += " ) up (total"; + if (print_peaks) out += " | peak "; + out += " ) 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).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.0); + 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%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() + , print_peaks ? ("|" + 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() + , print_peaks ? ("|" + 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("s", (i->flags & peer_info::seed)?col_white:col_blue).c_str() + , color("u", (i->flags & peer_info::utp_socket)?col_white:col_blue).c_str() + , color("I", (i->flags & peer_info::i2p_socket)?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), "%3d %6s %6s|%6s|%6s%7s " + , i->requests_in_buffer + , add_suffix(i->used_send_buffer).c_str() + , add_suffix(i->used_receive_buffer).c_str() + , add_suffix(i->receive_buffer_size).c_str() + , add_suffix(i->receive_buffer_watermark).c_str() + , add_suffix(i->queue_bytes).c_str()); + 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; +} + +// returns the number of lines printed +int print_peer_legend(std::string& out, int max_lines) +{ +#ifdef _MSC_VER +#pragma warning(push, 1) +// warning C4566: character represented by universal-character-name '\u256F' +// cannot be represented in the current code page (1252) +#pragma warning(disable: 4566) +#endif + + std::array lines{{ + " we are interested \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502\u2502\u2502\u2570\u2500\u2500\u2500 incoming\x1b[K\n", + " we have choked \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502\u2502\u2570\u2500\u2500\u2500 resume data\x1b[K\n", + "remote is interested \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502\u2570\u2500\u2500\u2500 local peer discovery\x1b[K\n", + " remote has choked \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2570\u2500\u2500\u2500 DHT\x1b[K\n", + " supports extensions \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2570\u2500\u2500\u2500 peer exchange\x1b[K\n", + " outgoing connection \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502 \u2570\u2500\u2500\u2500 tracker\x1b[K\n", + " on parole \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2502\u2570\u2500\u253c\u253c\u2534\u2500\u2500\u2500 network\x1b[K\n", + " optimistic unchoke \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2570\u2500\u2500\u253c\u2534\u2500\u2500\u2500 rate limit\x1b[K\n", + " snubbed \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2570\u2500\u2500\u2500\u2534\u2500\u2500\u2500 disk\x1b[K\n", + " upload only \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2570\u2500\u2500\u2500 i2p\x1b[K\n", + " end-game mode \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2570\u2500\u2500\u2500 uTP\x1b[K\n", + " obfuscation level \u2500\u2500\u2500\u256f\u2502\u2570\u2500\u2500\u2500 seed\x1b[K\n", + " hole-punched \u2500\u2500\u2500\u256f\x1b[K\n", + }}; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + char const* ip = " "; + char const* indentation = " "; + int ret = 0; + for (auto const& l : lines) + { + if (max_lines <= 0) break; + ++ret; + out += indentation; + if (print_ip) + out += ip; + if (print_local_ip) + out += ip; + out += l; + } + return ret; +} + +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; +bool exit_on_finish = false; + +bool share_mode = false; + +bool quit = false; + +#ifndef _WIN32 +void signal_handler(int) +{ + // make the main loop terminate + quit = true; +} +#endif + +// 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 type) +{ + for (int i = start; i < start + num; ++i) + { + char const* name = lt::name_for_setting(i); + if (!name || name[0] == '\0') continue; + std::printf("%s=<%s>\n", name, type); + } +} + +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 lt::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}, + {"write_through"_sv, settings_pack::write_through}, + {"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::info_hash_t const& info_hash) +{ + return path_append(save_path, path_append(".resume" + , to_hex(info_hash.get_best()) + ".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 (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_hashes), 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_hashes()), 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); +#ifdef TORRENT_WINDOWS + std::tm const* timeinfo = localtime(&t); +#else + std::tm buf; + std::tm const* timeinfo = localtime_r(&t, &buf); +#endif + 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"); + + static auto const first_ts = a->timestamp(); + + if (g_log_file) + std::fprintf(g_log_file, "[%" PRId64 "] %s\n" + , std::int64_t(duration_cast(a->timestamp() - first_ts).count()) + , 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(), int(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(), s->timestamp()); + 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_SSL_PEERS + 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; + } + + 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()) + { + auto port = peer.find_last_of(':'); + if (port != std::string::npos) + { + peer[port++] = '\0'; + char const* ip = peer.data(); + int const peer_port = atoi(peer.data() + port); + error_code ec; + if (peer_port > 0) + h.connect_peer(tcp::endpoint(make_address(ip, ec), std::uint16_t(peer_port))); + } + } + } + } + + 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; + if (exit_on_finish) quit = true; + } + + 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_hashes), buf); + } + + 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; + } + + 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; + } + + if (state_update_alert* p = alert_cast(a)) + { + view.update_torrents(std::move(p->status)); + return true; + } + + 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 + , std::vector const& peers + , std::string& out) +{ + using namespace lt; + + char str[1024]; + int const piece = static_cast(pp.piece_index); + int const num_blocks = pp.blocks_in_piece; + + std::snprintf(str, sizeof(str), "%5d:[", piece); + out += str; + string_view last_color; + for (int j = 0; j < num_blocks; ++j) + { + int const index = peer_index(pp.blocks[j].peer(), peers) % 36; + bool const snubbed = index >= 0 ? bool(peers[std::size_t(index)].flags & lt::peer_info::snubbed) : false; + char const* chr = " "; + char const* color = ""; + + 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; +} + +void print_usage() +{ + std::fprintf(stderr, R"(usage: client_test [OPTIONS] [TORRENT|MAGNETURL] +OPTIONS: + +CLIENT OPTIONS + -h print this message + -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 + -1 exit on first torrent completing (useful for benchmarks))" +#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 +)") ; +} + +} // anonymous namespace + +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) + { + print_usage(); + 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 + + std::vector in; + if (load_file(".ses_state", in)) + params = read_session_params(in, session_handle::save_dht_state); + + params.settings.set_bool(settings_pack::dht_privacy_lookups, true); +#endif + + auto& settings = params.settings; + + 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, "string"); + print_settings(settings_pack::bool_type_base + , settings_pack::num_bool_settings, "bool"); + print_settings(settings_pack::int_type_base + , settings_pack::num_int_settings, "int"); + 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, std::size_t(equal - start)); + char const* value = equal + 1; + + assign_setting(settings, key, value); + continue; + } + + // command line switches that don't take an argument + switch (argv[i][1]) + { + case 'k': settings = lt::high_performance_seed(); continue; + case 'G': seed_mode = true; continue; + case 'O': stats_enabled = true; continue; + case '1': exit_on_finish = true; continue; +#ifdef TORRENT_UTP_LOG_ENABLE + case 'q': + lt::set_utp_stream_logging(true); + continue; +#endif + case 'Q': share_mode = true; continue; + case 'Y': rate_limit_locals = true; continue; + case '0': params.disk_io_constructor = lt::disabled_disk_io_constructor; continue; + case 'h': print_usage(); return 0; + } + + // if there's a flag but no argument following, ignore it + if (argc == i + 1) + { + std::fprintf(stderr, "invalid command line argument or missing parameter: %s\n", argv[i]); + return 1; + } + 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 's': save_path = make_absolute_path(arg); break; + 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 '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\.]+)\s*-\s*([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 = make_address_v4(m[1]); + address_v4 last = make_address_v4(m[2]); + loaded_ip_filter.add_rule(start, last, stoi(m[3]) <= 127 ? lt::ip_filter::blocked : 0); + } + } + } + } + break; + case 'T': max_connections_per_torrent = atoi(arg); break; + case 'r': peer = arg; 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(make_address_v4("0.0.0.0") + , make_address_v4("255.255.255.255") + , 1 << static_cast(lt::session::global_peer_class_id)); + pcf.add_rule(make_address_v6("::") + , make_address_v6("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; + +#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 echo_(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 (auto const& s : seeds) + h.remove_url_seed(s); + + seeds = h.http_seeds(); + for (auto const& s : seeds) + h.remove_http_seed(s); + } + + 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 echo_(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_hashes); + 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 == 'I') print_peers_legend = !print_peers_legend; + 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 == '5') print_peaks = !print_peaks; + 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 == '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 [W] remove all web seeds +[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 [I] toggle show peer flag legend + +COLUMN OPTIONS +[1] toggle IP column [2] toggle show peer connection attempts +[3] toggle timers column [4] toggle block progress column +[5] toggle print peak rates [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); + + torrent_handle h = view.get_active_handle(); + +#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(); + + if (!print_matrix) { + 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_peers_legend) + { + pos += print_peer_legend(out, 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 (pos + 1 >= terminal_height) break; + if (!ep.enabled) continue; + for (lt::protocol_version const v : {lt::protocol_version::V1, lt::protocol_version::V2}) + { + if (!s.info_hashes.has(v)) continue; + auto const& av = ep.info_hashes[v]; + + std::snprintf(str, sizeof(str), " [%2d] fails: %-3d (%-3d) %s %5d \"%s\" %s\x1b[K\n" + , idx + , av.fails, ae.fail_limit + , to_string(int(total_seconds(av.next_announce - now)), 8).c_str() + , av.min_announce > now ? int(total_seconds(av.min_announce - now)) : 0 + , av.last_error ? av.last_error.message().c_str() : "" + , av.message.c_str()); + out += str; + pos += 1; + // we only need to show this error once, not for every + // endpoint + if (av.last_error == boost::asio::error::host_not_found) + goto done; + } + } +done: + + 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) + { + std::vector queue = h.get_download_queue(); + + int p = 0; // this is horizontal position + for (lt::partial_piece_info const& i : queue) + { + if (pos + 3 >= terminal_height) break; + + print_piece(i, peers, out); + + int const 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 downloading | %s %s writing | %s %s flushed | %s %s snubbed | = requested\x1b[K\n" + , esc("33;7"), esc("0") // downloading + , esc("36;7"), esc("0") // writing + , 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 const file_progress = h.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) + { + auto const idx = std::size_t(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.0); + 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) + { + return st.handle.is_valid() && st.has_metadata && st.need_save_resume; + }, {}); + + 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"); + { + std::vector out = write_session_params_buf(ses.session_state(lt::session::save_dht_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..ee61821 --- /dev/null +++ b/examples/cmake/FindLibtorrentRasterbar.cmake @@ -0,0 +1,190 @@ +# - 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() + + set(LibtorrentRasterbar_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) + + find_package(Boost QUIET REQUIRED) + if (Boost_MAJOR_VERSION LESS_EQUAL 1 AND Boost_MINOR_VERSION LESS 69) + if (NOT Boost_SYSTEM_FOUND) + find_package(Boost QUIET REQUIRED COMPONENTS system) + endif() + list(APPEND LibtorrentRasterbar_LIBRARIES Boost::system) + endif() + + 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..b470eaa --- /dev/null +++ b/examples/connection_tester.cpp @@ -0,0 +1,1217 @@ +/* + +Copyright (c) 2010-2020, Arvid Norberg +Copyright (c) 2015, Mike Tzou +Copyright (c) 2016-2017, Steven Siloti +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016, 2018, 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/peer_id.hpp" +#include "libtorrent/io_context.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/string_view.hpp" +#include "libtorrent/session.hpp" // for default_disk_io_constructor +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_ASIO_DYN_LINK +#include +#endif + +namespace { + +using namespace lt; +using namespace lt::aux; // for write_* and read_* +using lt::make_address_v4; + +using namespace std::placeholders; + +void generate_block(span buffer, piece_index_t const piece + , int const offset) +{ + std::uint32_t const fill = static_cast( + (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 + std::size_t 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_context& ios, int piece_count, 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(piece_count) + , start_time(clock_type::now()) + , churn(churn_) + , corrupt(corrupt_) + , endpoint(ep) + , restarting(false) + { + corruption_counter = rand() % 1000; + if (seed) ++num_seeds; + pieces.reserve(std::size_t(piece_count)); + start_conn(); + } + + void start_conn() + { + if (local_bind) + { + error_code ec; + s.open(endpoint.protocol(), ec); + if (ec) + { + close("ERROR OPEN", ec); + return; + } + tcp::endpoint bind_if(address_v4( + (127 << 24) + unsigned (local_if_counter + 1)), 0); + ++local_if_counter; + s.bind(bind_if, ec); + if (ec) + { + close("ERROR BIND", 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", 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, info_hash, 20); + std::generate(h + 48, h + 68, [] { return char(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", ec); + return; + } + + // read handshake + boost::asio::async_read(s, boost::asio::buffer(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", ec); + return; + } + + // buffer is the full 68 byte handshake + // look at the extension bits + + fast_extension = (reinterpret_cast(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, std::size_t(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 = reinterpret_cast(buffer); + write_uint32(len + 1, ptr); + write_uint8(5, ptr); + memset(ptr, 255, std::size_t(len)); + ptr += len; + // unchoke + write_uint32(1, ptr); + write_uint8(1, ptr); + boost::asio::async_write(s, boost::asio::buffer(buffer, std::size_t(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", ec); + return; + } + + // read message + boost::asio::async_read(s, boost::asio::buffer(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 = static_cast(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", ec); + return; + } + + work_download(); + } + + void close(char const* msg, error_code const& ec) + { + end_time = clock_type::now(); + char tmp[1024]; + std::snprintf(tmp, sizeof(tmp), "%s: %s", msg, ec ? ec.message().c_str() : ""); + 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().c_str() + , s.local_endpoint(e).port()); + else + std::snprintf(ep_str, sizeof(ep_str), "%s:%d", addr.to_string().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(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", ec); + return; + } + char* ptr = reinterpret_cast(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(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", ec); + return; + } + char* ptr = reinterpret_cast(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(aux::read_int32(ptr)); + int const start = aux::read_int32(ptr); + int const length = aux::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(std::size_t(num_pieces)); + for (std::size_t i = 0; i < pieces.size(); ++i) + pieces[i] = piece_index_t(int(i)); + std::shuffle(pieces.begin(), pieces.end(), rng); + } + else if (msg == 4) // have + { + piece_index_t const piece(aux::read_int32(ptr)); + if (pieces.empty()) pieces.push_back(piece); + else pieces.insert(pieces.begin() + (unsigned(rand()) % pieces.size()), piece); + } + else if (msg == 5) // bitfield + { + pieces.reserve(std::size_t(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_int32(ptr)); + int start = read_int32(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(aux::read_int32(ptr)); + int start = aux::read_int32(ptr); + + if (churn && (blocks_received % churn) == 0) { + outstanding_requests = 0; + restarting = true; + s.close(); + return; + } + if ((start + int(bytes_transferred)) / 0x4000 == blocks_per_piece) + { + write_have(piece); + return; + } + } + else if (msg == 13) // suggest + { + piece_index_t const piece(aux::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(aux::read_int32(ptr)); + int start = aux::read_int32(ptr); + int length = aux::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(aux::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 const* buf = reinterpret_cast(ptr); + std::uint32_t const fill = static_cast( + (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, std::size_t(ptr - write_buf_proto)); + vec[1] = boost::asio::buffer(write_buffer, std::size_t(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)); + } +}; + +[[noreturn]] 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" + " -t the file to save the .torrent file to\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::aux::vector* output + , lt::file_storage const& fs + , piece_index_t const start_piece + , piece_index_t const end_piece + , bool print) +{ + if (print) std::fprintf(stderr, "\n"); + std::uint32_t piece[0x4000 / 4]; + int const piece_size = fs.piece_length(); + + std::vector files = fs.map_block(start_piece, 0 + , std::min(static_cast(end_piece - start_piece) * std::int64_t(piece_size) + , fs.total_size() - static_cast(start_piece) * std::int64_t(piece_size))); + + 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); + + // if any part of this block overlaps with a pad-file, we need to + // clear those bytes to 0 + for (int k = 0; k < 0x4000; ) + { + if (files.empty()) + { + TORRENT_ASSERT(i == prev(end_piece)); + TORRENT_ASSERT(k > 0); + TORRENT_ASSERT(k < 0x4000); + // this is the last piece of the torrent, and the piece + // extends a bit past the end of the last file. This part + // should be truncated + ph.update(reinterpret_cast(piece), k); + goto out; + } + auto& f = files.front(); + int const range = int(std::min(std::int64_t(0x4000 - k), f.size)); + if (fs.pad_file_at(f.file_index)) + std::memset(reinterpret_cast(piece) + k, 0, std::size_t(range)); + + f.offset += range; + f.size -= range; + k += range; + if (f.size == 0) files.erase(files.begin()); + } + ph.update(reinterpret_cast(piece), 0x4000); + } +out: + (*output)[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 %% ", double(delta_piece * 100) / double(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) +{ + 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, lt::create_torrent::v1_only); + + num_pieces = t.num_pieces(); + + int const num_threads = std::thread::hardware_concurrency() + ? int(std::thread::hardware_concurrency()) : 4; + std::printf("hashing in %d threads\n", num_threads); + + std::vector threads; + threads.reserve(std::size_t(num_threads)); + lt::aux::vector hashes{static_cast(num_pieces)}; + for (int i = 0; i < num_threads; ++i) + { + threads.emplace_back(&hasher_thread, &hashes, t.files() + , piece_index_t(i * num_pieces / num_threads) + , piece_index_t((i + 1) * num_pieces / num_threads) + , i == 0); + } + + for (auto& i : threads) + i.join(); + + for (auto i : t.files().piece_range()) + t.set_hash(i, hashes[i]); + + bencode(std::back_inserter(buf), t.generate()); +} + +void write_handler(file_storage const& fs + , disk_interface& disk, storage_holder& st + , piece_index_t& piece, int& offset + , lt::storage_error const& error) +{ + if (error) + { + std::fprintf(stderr, "storage error: %s\n", error.ec.message().c_str()); + return; + } + + + if (static_cast(piece) & 1) + { + std::fprintf(stderr, "\r%.1f %% " + , double(static_cast(piece) * 100) / double(fs.num_pieces())); + } + + if (piece >= fs.end_piece()) return; + offset += 0x4000; + if (offset >= fs.piece_size(piece)) + { + offset = 0; + ++piece; + } + if (piece >= fs.end_piece()) + { + disk.abort(false); + return; + } + + std::uint32_t buffer[0x4000 / 4]; + generate_block(buffer, piece, offset); + + int const left_in_piece = fs.piece_size(piece) - offset; + if (left_in_piece <= 0) return; + + disk.async_write(st, { piece, offset, std::min(left_in_piece, 0x4000)} + , reinterpret_cast(buffer) + , std::shared_ptr() + , [&](lt::storage_error const& e) + { write_handler(fs, disk, st, piece, offset, e); }); + + disk.submit_jobs(); +} + +void generate_data(char const* path, torrent_info const& ti) +{ + io_context ios; + counters stats_counters; + settings_pack sett = default_settings(); + std::unique_ptr disk = default_disk_io_constructor(ios, sett, stats_counters); + + file_storage const& fs = ti.files(); + + aux::vector priorities; + sha1_hash info_hash; + storage_params params{ + fs, + nullptr, + path, + storage_mode_sparse, + priorities, + info_hash + }; + + storage_holder st = disk->new_torrent(params, std::shared_ptr()); + + piece_index_t piece(0); + int offset = 0; + + std::uint32_t buffer[0x4000 / 4]; + generate_block(buffer, piece, offset); + + disk->async_write(st, { piece, offset, std::min(fs.piece_size(piece), 0x4000)} + , reinterpret_cast(buffer) + , std::shared_ptr() + , [&](lt::storage_error const& error) + { write_handler(fs, *disk, st, piece, offset, error); }); + + // keep 10 writes in flight at all times + for (int i = 0; i < 10; ++i) + { + write_handler(fs, *disk, st, piece, offset, lt::storage_error()); + } + + disk->submit_jobs(); + + ios.run(); +} + +void io_thread(io_context* ios) try +{ + ios->run(); +} +catch (std::exception const& e) +{ + std::fprintf(stderr, "ERROR: %s\n", e.what()); +} + +} // anonymous namespace + +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; + 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; + } + + if (argc == 0) + { + std::fprintf(stderr, "missing argument for option: %s\n", optname); + break; + } + + char const* opt = argv[0]; + ++argv; + --argc; + + switch (optname[1]) + { + case 's': size = atoi(opt); break; + case 'n': num_files = atoi(opt); break; + case 'N': num_torrents = atoi(opt); break; + case 't': torrent_file = opt; break; + case 'T': trackers.push_back(opt); break; + case 'P': data_path = opt; break; + case 'c': num_connections = atoi(opt); break; + case 'p': destination_port = atoi(opt); break; + case 'd': destination_ip = opt; break; + case 'r': churn = atoi(opt); 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() ); + + 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, lt::create_torrent::v1_only); + 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 = make_address_v4(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_uint(); + 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(std::size_t(num_connections)); + int const num_threads = 2; + io_context ios[num_threads]; + lt::sha1_hash const ih = ti.info_hash(); + 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, ih.data(), seed, churn, corrupt)); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ios[i % num_threads].poll_one(); + } + + std::thread t1(&io_thread, &ios[0]); + std::thread t2(&io_thread, &ios[1]); + + t1.join(); + t2.join(); + + double up = 0.0; + double down = 0.0; + std::int64_t total_sent = 0; + std::int64_t total_received = 0; + + for (peer_conn* p : conns) + { + int time = int(total_milliseconds(p->end_time - p->start_time)); + if (time == 0) time = 1; + total_sent += p->blocks_sent; + total_received += p->blocks_received; + up += (std::int64_t(p->blocks_sent) * 0x4000) / time / 1000.0; + down += (std::int64_t(p->blocks_received) * 0x4000) / time / 1000.0; + 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.0 / double(ti.total_size()) + , total_received * 0x4000 * 100.0 / double(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..606e4f0 --- /dev/null +++ b/examples/custom_storage.cpp @@ -0,0 +1,328 @@ +/* + +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2019-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 +#include "libtorrent/libtorrent.hpp" + +#include + +namespace { + +// -- example begin +struct temp_storage +{ + explicit temp_storage(lt::file_storage const& fs) : m_files(fs) {} + + lt::span readv(lt::peer_request const r, lt::storage_error& ec) const + { + auto const i = m_file_data.find(r.piece); + if (i == m_file_data.end()) + { + ec.operation = lt::operation_t::file_read; + ec.ec = boost::asio::error::eof; + return {}; + } + if (int(i->second.size()) <= r.start) + { + ec.operation = lt::operation_t::file_read; + ec.ec = boost::asio::error::eof; + return {}; + } + return { i->second.data() + r.start, std::min(r.length, int(i->second.size()) - r.start) }; + } + void writev(lt::span const b, lt::piece_index_t const piece, int const offset) + { + auto& data = m_file_data[piece]; + if (data.empty()) + { + // allocate the whole piece, otherwise we'll invalidate the pointers + // we have returned back to libtorrent + int const size = piece_size(piece); + data.resize(std::size_t(size)); + } + TORRENT_ASSERT(offset + b.size() <= int(data.size())); + std::memcpy(data.data() + offset, b.data(), std::size_t(b.size())); + } + lt::sha1_hash hash(lt::piece_index_t const piece + , lt::span const block_hashes, lt::storage_error& ec) const + { + auto const i = m_file_data.find(piece); + if (i == m_file_data.end()) + { + ec.operation = lt::operation_t::file_read; + ec.ec = boost::asio::error::eof; + return {}; + } + if (!block_hashes.empty()) + { + int const piece_size2 = m_files.piece_size2(piece); + int const blocks_in_piece2 = m_files.blocks_in_piece2(piece); + char const* buf = i->second.data(); + std::int64_t offset = 0; + for (int k = 0; k < blocks_in_piece2; ++k) + { + lt::hasher256 h2; + std::ptrdiff_t const len2 = std::min(lt::default_block_size, int(piece_size2 - offset)); + h2.update({ buf, len2 }); + buf += len2; + offset += len2; + block_hashes[k] = h2.final(); + } + } + return lt::hasher(i->second).final(); + } + lt::sha256_hash hash2(lt::piece_index_t const piece, int const offset, lt::storage_error& ec) + { + auto const i = m_file_data.find(piece); + if (i == m_file_data.end()) + { + ec.operation = lt::operation_t::file_read; + ec.ec = boost::asio::error::eof; + return {}; + } + + int const piece_size = m_files.piece_size2(piece); + + std::ptrdiff_t const len = std::min(lt::default_block_size, piece_size - offset); + + lt::span b = {i->second.data() + offset, len}; + return lt::hasher256(b).final(); + } + +private: + int piece_size(lt::piece_index_t piece) const + { + int const num_pieces = static_cast((m_files.total_size() + m_files.piece_length() - 1) / m_files.piece_length()); + return static_cast(piece) < num_pieces - 1 + ? m_files.piece_length() : static_cast(m_files.total_size() - std::int64_t(num_pieces - 1) * m_files.piece_length()); + } + + lt::file_storage const& m_files; + std::map> m_file_data; +}; + +lt::storage_index_t pop(std::vector& q) +{ + TORRENT_ASSERT(!q.empty()); + lt::storage_index_t const ret = q.back(); + q.pop_back(); + return ret; +} + +struct temp_disk_io final : lt::disk_interface + , lt::buffer_allocator_interface +{ + explicit temp_disk_io(lt::io_context& ioc): m_ioc(ioc) {} + + void settings_updated() override {} + + lt::storage_holder new_torrent(lt::storage_params const& params + , std::shared_ptr const&) override + { + lt::storage_index_t const idx = m_free_slots.empty() + ? m_torrents.end_index() + : pop(m_free_slots); + auto storage = std::make_unique(params.files); + if (idx == m_torrents.end_index()) m_torrents.emplace_back(std::move(storage)); + else m_torrents[idx] = std::move(storage); + return lt::storage_holder(idx, *this); + } + + void remove_torrent(lt::storage_index_t const idx) override + { + m_torrents[idx].reset(); + m_free_slots.push_back(idx); + } + + void abort(bool) override {} + + void async_read(lt::storage_index_t storage, lt::peer_request const& r + , std::function handler + , lt::disk_job_flags_t) override + { + // this buffer is owned by the storage. It will remain valid for as + // long as the torrent remains in the session. We don't need any lifetime + // management of it. + lt::storage_error error; + lt::span b = m_torrents[storage]->readv(r, error); + + post(m_ioc, [handler, error, b, this] + { handler(lt::disk_buffer_holder(*this, const_cast(b.data()), int(b.size())), error); }); + } + + bool async_write(lt::storage_index_t storage, lt::peer_request const& r + , char const* buf, std::shared_ptr + , std::function handler + , lt::disk_job_flags_t) override + { + lt::span const b = { buf, r.length }; + + m_torrents[storage]->writev(b, r.piece, r.start); + + post(m_ioc, [=]{ handler(lt::storage_error()); }); + return false; + } + + void async_hash(lt::storage_index_t storage, lt::piece_index_t const piece + , lt::span block_hashes, lt::disk_job_flags_t + , std::function handler) override + { + lt::storage_error error; + lt::sha1_hash const hash = m_torrents[storage]->hash(piece, block_hashes, error); + post(m_ioc, [=]{ handler(piece, hash, error); }); + } + + void async_hash2(lt::storage_index_t storage, lt::piece_index_t const piece + , int const offset, lt::disk_job_flags_t + , std::function handler) override + { + lt::storage_error error; + lt::sha256_hash const hash = m_torrents[storage]->hash2(piece, offset, error); + post(m_ioc, [=]{ handler(piece, hash, error); }); + } + + void async_move_storage(lt::storage_index_t, std::string p, lt::move_flags_t + , std::function handler) override + { + post(m_ioc, [=]{ + handler(lt::status_t::fatal_disk_error, p + , lt::storage_error(lt::error_code(boost::system::errc::operation_not_supported, lt::system_category()))); + }); + } + + void async_release_files(lt::storage_index_t, std::function) override {} + + void async_delete_files(lt::storage_index_t, lt::remove_flags_t + , std::function handler) override + { + post(m_ioc, [=]{ handler(lt::storage_error()); }); + } + + void async_check_files(lt::storage_index_t + , lt::add_torrent_params const* + , lt::aux::vector + , std::function handler) override + { + post(m_ioc, [=]{ handler(lt::status_t::no_error, lt::storage_error()); }); + } + + void async_rename_file(lt::storage_index_t + , lt::file_index_t const idx + , std::string const name + , std::function handler) override + { + post(m_ioc, [=]{ handler(name, idx, lt::storage_error()); }); + } + + void async_stop_torrent(lt::storage_index_t, std::function handler) override + { + post(m_ioc, handler); + } + + void async_set_file_priority(lt::storage_index_t + , lt::aux::vector prio + , std::function)> handler) override + { + post(m_ioc, [=]{ + handler(lt::storage_error(lt::error_code( + boost::system::errc::operation_not_supported, lt::system_category())), std::move(prio)); + }); + } + + void async_clear_piece(lt::storage_index_t, lt::piece_index_t index + , std::function handler) override + { + post(m_ioc, [=]{ handler(index); }); + } + + // implements buffer_allocator_interface + void free_disk_buffer(char*) override + { + // never free any buffer. We only return buffers owned by the storage + // object + } + + void update_stats_counters(lt::counters&) const override {} + + std::vector get_status(lt::storage_index_t) const override + { return {}; } + + void submit_jobs() override {} + +private: + + lt::aux::vector, lt::storage_index_t> m_torrents; + + // slots that are unused in the m_torrents vector + std::vector m_free_slots; + + // callbacks are posted on this + lt::io_context& m_ioc; +}; + +std::unique_ptr temp_disk_constructor( + lt::io_context& ioc, lt::settings_interface const&, lt::counters&) +{ + return std::make_unique(ioc); +} +// -- example end + +} // anonymous namespace + +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_params ses_params; + ses_params.disk_io_constructor = temp_disk_constructor; + lt::session s(ses_params); + 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/dump_bdecode.cpp b/examples/dump_bdecode.cpp new file mode 100644 index 0000000..44acc9b --- /dev/null +++ b/examples/dump_bdecode.cpp @@ -0,0 +1,120 @@ +/* + +Copyright (c) 2019-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 +#include + +#include "libtorrent/bencode.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/span.hpp" + +namespace { + +std::vector load_file(char const* filename) +{ + std::fstream in; + in.exceptions(std::ifstream::failbit); + in.open(filename, 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(), int(size)); + return ret; +} + +[[noreturn]] void print_usage() +{ + std::cerr << R"(usage: dump_bdecode file [options] + OPTIONS: + --items-limit set the upper limit of the number of bencode items + in the bencoded file. + --depth-limit set the recursion limit in the bdecoder +)"; + std::exit(1); +} + +} // anonymous namespace + +int main(int argc, char const* argv[]) try +{ + lt::span args(argv, argc); + + // strip executable name + args = args.subspan(1); + + if (args.empty()) print_usage(); + + char const* filename = args[0]; + args = args.subspan(1); + + int max_decode_depth = 1000; + int max_decode_tokens = 2000000; + + using namespace lt::literals; + + while (!args.empty()) + { + if (args[0] == "--items-limit"_sv && args.size() > 1) + { + max_decode_tokens = atoi(args[1]); + args = args.subspan(2); + } + else if (args[0] == "--depth-limit"_sv && args.size() > 1) + { + max_decode_depth = atoi(args[1]); + args = args.subspan(2); + } + else + { + std::cerr << "unknown option: " << args[0] << "\n"; + print_usage(); + } + } + + std::vector buf = load_file(filename); + int pos = -1; + lt::error_code ec; + lt::bdecode_node const e = lt::bdecode(buf, ec, &pos, max_decode_depth + , max_decode_tokens); + + if (ec) { + std::cerr << "failed to decode: '" << ec.message() << "' at character: " << pos<< "\n"; + return 1; + } + + std::printf("%s\n", print_entry(e).c_str()); +} +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..1077ef2 --- /dev/null +++ b/examples/dump_torrent.cpp @@ -0,0 +1,193 @@ +/* + +Copyright (c) 2018, Steven Siloti +Copyright (c) 2003-2004, 2008-2010, 2013, 2015-2020, Arvid Norberg +Copyright (c) 2018, 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 // for snprintf +#include // for PRId64 et.al. +#include +#include + +#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" + +namespace { + +[[noreturn]] void print_usage() +{ + std::cerr << R"(usage: dump_torrent torrent-file [options] + OPTIONS: + --items-limit set the upper limit of the number of bencode items + in the torrent file. + --depth-limit set the recursion limit in the bdecoder + --show-padfiles show pad files in file list + --max-pieces set the upper limit on the number of pieces to + load in the torrent. + --max-size reject files larger than this size limit +)"; + std::exit(1); +} + +} + +int main(int argc, char const* argv[]) try +{ + lt::span 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(first) + , static_cast(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/examples/make_torrent.cpp b/examples/make_torrent.cpp new file mode 100644 index 0000000..f1d517f --- /dev/null +++ b/examples/make_torrent.cpp @@ -0,0 +1,319 @@ +/* + +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2004, 2008-2013, 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +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/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/create_torrent.hpp" + +#include +#include +#include +#include +#include + +#ifdef TORRENT_WINDOWS +#include // for _getcwd +#endif + +namespace { + +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(), int(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 ""; + + auto len = 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; +} + +[[noreturn]] 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 +-T Include file timestamps in the .torrent file. +)"; + std::exit(1); +} + +} // anonymous namespace + +int main(int argc_, char const* argv_[]) try +{ + lt::span args(argv_, argc_); + std::string creator_str = "libtorrent"; + std::string comment_str; + + if (args.size() < 2) print_usage(); + + std::vector web_seeds; + std::vector trackers; + std::vector collections; + std::vector 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(); + + 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; + case 'T': + flags |= lt::create_torrent::modification_time; + continue; + } + + if (args.size() < 2) print_usage(); + + 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(); + } + std::stringstream hash(args[1]); + lt::sha1_hash ih; + hash >> ih; + if (hash.fail()) { + std::cerr << "invalid info-hash for -S\n"; + print_usage(); + } + similar.push_back(ih); + break; + } + default: + print_usage(); + } + 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 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(), int(torrent.size())); + } + else { + std::cout.write(torrent.data(), int(torrent.size())); + } + + 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..d6d9be6 --- /dev/null +++ b/examples/print.cpp @@ -0,0 +1,553 @@ +#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), std::size_t(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; +} + +namespace { +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(); + + if (width <= 0) return bar; + + bar.reserve(std::size_t(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) +{ + if (width <= 0) return {}; + + // print two rows of pieces at a time + int piece = 0; + ++*height; + std::string ret; + ret.reserve(std::size_t((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/run_benchmarks.py b/examples/run_benchmarks.py new file mode 100755 index 0000000..3dcbe45 --- /dev/null +++ b/examples/run_benchmarks.py @@ -0,0 +1,438 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 +from __future__ import print_function + +import sys +import os +import resource +import shutil +import shlex +import time +import subprocess +import random +import signal +import hashlib + +# this is a disk I/O benchmark script. It runs benchmarks +# over different number of peers. + +# to set up the test, build the example directory in release +# and stage client_test and connection_tester to the examples directory: +# +# bjam link=static release debug-symbols=on stage +# +# make sure gnuplot is installed. + +# the following lists define the space tests will be run in + +peers = [50, 200, 500, 1000] +# builds = ['rtorrent', 'utorrent', 'libtorrent'] +builds = ['libtorrent'] + +# the number of peers for the filesystem test. The +# idea is to stress test the filesystem by using a lot +# of peers, since each peer essentially is a separate +# read location on the platter +default_peers = peers[1] + +# the amount of cache for the filesystem test +# 5.5 GiB of cache +default_cache = 400000 + +# the number of seconds to run each test. It's important that +# this is shorter than what it takes to finish downloading +# the test torrent, since then the average rate will not +# be representative of the peak anymore +# this has to be long enough to download a full copy +# of the test torrent. It's also important for the +# test to be long enough that the warming up of the +# disk cache is not a significant part of the test, +# since download rates will be extremely high while downloading +# into RAM +test_duration = 100 + +utorrent_version = 'utorrent-server-alpha-v3_3' + +# make sure the environment is properly set up +try: + if os.name == 'posix': + resource.setrlimit(resource.RLIMIT_NOFILE, (4000, 5000)) +except Exception: + if resource.getrlimit(resource.RLIMIT_NOFILE)[0] < 4000: + print('please set ulimit -n to at least 4000') + sys.exit(1) + + +def build_stage_dirs(): + ret = [] + for i in builds[2:3]: + ret.append('stage_%s' % i) + return ret + + +# make sure we have all the binaries available +binaries = ['client_test', 'connection_tester'] +for b in build_stage_dirs(): + for i in binaries: + p = os.path.join(b, i) + if not os.path.exists(p): + print('make sure "%s" is available in ./%s' % (i, b)) + sys.exit(1) + +# make sure we have a test torrent +if not os.path.exists('test.torrent'): + print('generating test torrent') + # generate a 100 GB torrent, to make sure it won't all fit in physical RAM + os.system('./connection_tester gen-torrent -s 100000 -t test.torrent') + +# use a new port for each test to make sure they keep working +# this port is incremented for each test run +port = 10000 + random.randint(0, 40000) + +try: + os.mkdir('benchmark-dir') +except Exception: + pass + + +def clear_caches(): + if 'linux' in sys.platform: + os.system('sync') + try: + open('/proc/sys/vm/drop_caches', 'w').write('3') + except Exception: + pass + elif 'darwin' in sys.platform: + os.system('purge') + + +def build_utorrent_commandline(config, port): + num_peers = config['num-peers'] + torrent_path = config['torrent'] + target_folder = build_target_folder(config) + + try: + os.mkdir('utorrent_session') + except Exception: + pass + with open('utorrent_session/settings.dat', 'w+') as cfg: + + cfg.write('d') + cfg.write('20:ul_slots_per_torrenti%de' % num_peers) + cfg.write('17:conns_per_torrenti%de' % num_peers) + cfg.write('14:conns_globallyi%de' % num_peers) + cfg.write('9:bind_porti%de' % port) + cfg.write('19:dir_active_download%d:%s' % (len(config['save-path']), + config['save-path'])) + cfg.write('19:diskio.sparse_filesi1e') + cfg.write('14:cache.overridei1e') + cfg.write('19:cache.override_sizei%de' % int(config['cache-size'] * + 16 / 1024)) + cfg.write('17:dir_autoload_flagi1e') + cfg.write('12:dir_autoload8:autoload') + cfg.write('11:logger_maski4294967295e') + cfg.write('1:vi0e') + cfg.write('12:webui.enablei1e') + cfg.write('19:webui.enable_listeni1e') + cfg.write('14:webui.hashword20:' + hashlib.sha1( + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaadmin').digest()) + cfg.write('10:webui.porti8080e') + cfg.write('10:webui.salt32:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + cfg.write('14:webui.username5:admin') + cfg.write('e') + + try: + os.mkdir('utorrent_session/autoload') + except Exception: + pass + try: + shutil.copy(torrent_path, 'utorrent_session/autoload/') + except Exception: + pass + return './%s/utserver -logfile %s/client.log -settingspath ' % \ + (utorrent_version, target_folder) + \ + 'utorrent_session' + + +def build_rtorrent_commandline(config, port): + num_peers = config['num-peers'] + torrent_path = config['torrent'] + target_folder = build_target_folder(config) + + if os.path.exists(target_folder): + add_command = '' + else: + try: + os.mkdir(target_folder) + except Exception: + pass + # it seems rtorrent may delete the original torrent when it's being added + try: + shutil.copy(torrent_path, target_folder) + except Exception: + pass + add_command = '-O load_start_verbose=%s/%s ' % (target_folder, torrent_path) + + return ('rtorrent -d %s -n -p %d-%d -O max_peers=%d -O max_uploads=%d %s -s ' + '%s -O max_memory_usage=128000000000') % ( + config['save-path'], port, port, num_peers, num_peers, add_command, target_folder) + + +def build_libtorrent_commandline(config, port): + num_peers = config['num-peers'] + torrent_path = config['torrent'] + target_folder = build_target_folder(config) + + return ('./client_test -k -O -F 500 --enable_upnp=0 --enable_natpmp=0 ' + '--enable_dht=0 --mixed_mode_algorithm=0 --peer_timeout=%d ' + '--listen_queue_size=%d --unchoke_slots_limit=%d -T %d ' + '--connections_limit=%d --cache_size=%d -s "%s" ' + '--listen_interfaces="0.0.0.0:%d" --aio_threads=%d ' + '-f %s/client.log %s') % ( + test_duration, num_peers, num_peers, num_peers, num_peers, config['cache-size'], + config['save-path'], port, config['disk-threads'], target_folder, torrent_path) + + +def build_commandline(config, port): + + if config['build'] == 'utorrent': + return build_utorrent_commandline(config, port) + + if config['build'] == 'rtorrent': + return build_rtorrent_commandline(config, port) + + if config['build'] == 'libtorrent': + return build_libtorrent_commandline(config, port) + + +def delete_files(files): + for i in files: + print('deleting %s' % i) + try: + os.remove(i) + except Exception: + try: + shutil.rmtree(i) + except Exception: + try: + if os.path.exists(i): + print('failed to delete %s' % i) + except Exception: + pass + + +def build_test_config(num_peers=default_peers, cache_size=default_cache, + test='download', build='libtorrent', profile='', disk_threads=16, + torrent='test.torrent', disable_disk=False): + config = {'test': test, 'save-path': os.path.join('.', 'benchmark-dir'), 'num-peers': num_peers, + 'cache-size': cache_size, 'build': build, 'profile': profile, + 'disk-threads': disk_threads, 'torrent': torrent, 'disable-disk': disable_disk} + return config + + +def build_target_folder(config): + + no_disk = '' + if config['disable-disk']: + no_disk = '_no-disk' + + return 'results_%s_%s_%d_%d_%d%s' % (config['build'], + config['test'], + config['num-peers'], + config['cache-size'], + config['disk-threads'], + no_disk) + + +def find_library(name): + paths = ['/usr/lib64/', '/usr/local/lib64/', '/usr/lib/', '/usr/local/lib/'] + + for p in paths: + try: + if os.path.exists(p + name): + return p + name + except Exception: + pass + return name + + +def find_binary(names): + paths = ['/usr/bin/', '/usr/local/bin/'] + for n in names: + for p in paths: + try: + if os.path.exists(p + n): + return p + n + except Exception: + pass + return names[0] + + +def run_test(config): + + j = os.path.join + + target_folder = build_target_folder(config) + if os.path.exists(target_folder): + print('results already exists, skipping test (%s)' % target_folder) + return + + print('\n\n*********************************') + print('* RUNNING TEST *') + print('*********************************\n\n') + print('%s %s' % (config['build'], config['test'])) + + # make sure any previous test file is removed + # don't clean up unless we're running a download-test, so that we leave the test file + # complete for a seed test. + delete_files(['utorrent_session/settings.dat', 'utorrent_session/settings.dat.old', 'asserts.log']) + if config['test'] == 'download' or config['test'] == 'dual': + delete_files([j(config['save-path'], 'test'), + '.ses_state', + j(config['save-path'], '.resume'), + 'utorrent_session', + '.dht_state', + 'rtorrent_session']) + + try: + os.mkdir(target_folder) + except Exception: + pass + + # save off the command line for reference + global port + cmdline = build_commandline(config, port) + binary = cmdline.split(' ')[0] + environment = None + if config['profile'] == 'tcmalloc': + environment = {'LD_PRELOAD': find_library('libprofiler.so.0'), + 'CPUPROFILE': j(target_folder, 'cpu_profile.prof')} + if config['profile'] == 'memory': + environment = {'LD_PRELOAD': find_library('libprofiler.so.0'), + 'HEAPPROFILE': j(target_folder, 'heap_profile.prof')} + if config['profile'] == 'perf': + cmdline = 'perf record -g --output=' + \ + j(target_folder, 'perf_profile.prof') + ' ' + cmdline + with open(j(target_folder, 'cmdline.txt'), 'w+') as f: + f.write(cmdline) + + with open(j(target_folder, 'config.txt'), 'w+') as f: + print(config, file=f) + + print('clearing disk cache') + clear_caches() + print('OK') + client_output = open(j(target_folder, 'client.output'), 'w+') + client_error = open(j(target_folder, 'client.error'), 'w+') + print('launching: %s' % cmdline) + client = subprocess.Popen( + shlex.split(cmdline), + stdout=client_output, + stdin=subprocess.PIPE, + stderr=client_error, + env=environment) + print('OK') + # enable disk stats printing + if config['build'] == 'libtorrent': + print('x', end=' ', file=client.stdin) + time.sleep(4) + test_dir = 'upload' if config['test'] == 'download' else 'download' if config['test'] == 'upload' else 'dual' + cmdline = './connection_tester %s -c %d -d 127.0.0.1 -p %d -t %s' % ( + test_dir, config['num-peers'], port, config['torrent']) + print('launching: %s' % cmdline) + tester_output = open(j(target_folder, 'tester.output'), 'w+') + tester = subprocess.Popen(shlex.split(cmdline), stdout=tester_output) + print('OK') + + time.sleep(2) + + print('\n') + i = 0 + while True: + time.sleep(1) + tester.poll() + if tester.returncode is not None: + print('tester terminated') + break + client.poll() + if client.returncode is not None: + print('client terminated') + break + print('\r%d / %d\x1b[K' % (i, test_duration), end=' ') + sys.stdout.flush() + i += 1 + # in download- and dual tests, connection_tester will exit once the + # client is done downloading. In upload tests, we'll upload for + # 'test_duration' number of seconds until we end the test + if config['test'] != 'download' and config['test'] != 'dual' and i >= test_duration: + break + print('\n') + + if client.returncode is None: + try: + print('killing client') + client.send_signal(signal.SIGINT) + except Exception: + pass + + time.sleep(10) + client.wait() + tester.wait() + tester_output.close() + client_output.close() + terminate = False + if tester.returncode != 0: + print('tester returned %d' % tester.returncode) + terminate = True + if client.returncode != 0: + print('client returned %d' % client.returncode) + terminate = True + + try: + shutil.copy('asserts.log', target_folder) + except Exception: + pass + + os.chdir(target_folder) + + if config['build'] == 'libtorrent': + # parse session stats + print('parsing session log') + os.system('python ../../tools/parse_session_stats.py client.log') + + os.chdir('..') + + if config['profile'] == 'tcmalloc': + print('analyzing CPU profile [%s]' % binary) + os.system('%s --pdf %s %s/cpu_profile.prof >%s/cpu_profile.pdf' % + (find_binary(['google-pprof', 'pprof']), binary, target_folder, target_folder)) + if config['profile'] == 'memory': + for i in range(1, 300): + profile = j(target_folder, 'heap_profile.prof.%04d.heap' % i) + try: + os.stat(profile) + except Exception: + break + print('analyzing heap profile [%s] %d' % (binary, i)) + os.system('%s --pdf %s %s >%s/heap_profile_%d.pdf' % + (find_binary(['google-pprof', 'pprof']), binary, profile, target_folder, i)) + if config['profile'] == 'perf': + print('analyzing CPU profile [%s]' % binary) + os.system(('perf report --input=%s/perf_profile.prof --threads --demangle --show-nr-samples ' + '>%s/profile.txt' % (target_folder, target_folder))) + + port += 1 + + if terminate: + sys.exit(1) + + +for b in builds: + for test in ['upload', 'download', 'dual']: + config = build_test_config(build=b, test=test, profile='perf') + run_test(config) + +for p in peers: + for test in ['upload', 'download', 'dual']: + config = build_test_config(num_peers=p, test=test, profile='perf') + run_test(config) diff --git a/examples/session_view.cpp b/examples/session_view.cpp new file mode 100644 index 0000000..1c4c7d2 --- /dev/null +++ b/examples/session_view.cpp @@ -0,0 +1,163 @@ +/* + +Copyright (c) 2018, Alden Torres +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/torrent_handle.hpp" + +#include // for std::max + +using lt::span; + +session_view::session_view() +{ + 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; +} + +std::int64_t session_view::value(int idx) const +{ + if (idx < 0) return 0; + return m_cnt[0][std::size_t(idx)]; +} + +std::int64_t session_view::prev_value(int idx) const +{ + if (idx < 0) return 0; + return m_cnt[1][std::size_t(idx)]; +} + +void session_view::render() +{ + char str[1024]; + int pos = 0; + + int y = m_position; + + using std::chrono::duration_cast; + double const seconds = duration_cast(m_timestamp[0] - m_timestamp[1]).count() / 1000.0; + + int const download_rate = int((value(m_recv_idx) - prev_value(m_recv_idx)) + / seconds); + int const upload_rate = int((value(m_sent_idx) - prev_value(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(value(m_failed_bytes_idx)).c_str() + , color(add_suffix(download_rate, "/s"), col_green).c_str() + , color(add_suffix(value(m_recv_idx)), col_green).c_str() + , color(to_string(int(value(m_limiter_up_queue_idx)), 3), col_red).c_str() + , color(to_string(int(value(m_limiter_down_queue_idx)), 3), col_green).c_str() + , int(value(m_num_peers_idx)) + , int(value(m_unchoked_idx)) + , int(value(m_unchoke_slots_idx)) + , int(value(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%% total: %s %*s\x1b[K" +#ifdef _WIN32 + , esc("40") +#else + , esc("48;5;238") +#endif + , esc("1") + , add_suffix(value(m_wasted_bytes_idx)).c_str() + , color(add_suffix(upload_rate, "/s"), col_red).c_str() + , color(add_suffix(value(m_sent_idx)), col_red).c_str() + , color(to_string(int(value(m_queued_reads_idx)), 3), col_red).c_str() + , color(to_string(int(value(m_queued_writes_idx)), 3), col_green).c_str() + , int((value(m_blocks_written_idx) - value(m_write_ops_idx)) * 100 + / std::max(std::int64_t(1), value(m_blocks_written_idx))) + , add_suffix(value(m_blocks_in_use_idx) * 16 * 1024).c_str() + , std::max(0, m_width - 85) + , 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(value(m_utp_idle)) + , int(value(m_utp_syn_sent)) + , int(value(m_utp_connected)) + , int(value(m_utp_fin_sent)) + , int(value(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 + , lt::clock_type::time_point const t) +{ + // only update the previous counters if there's been enough + // time since it was last updated + if (t - m_timestamp[1] > lt::seconds(2)) + { + 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..caa3d4f --- /dev/null +++ b/examples/session_view.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2014, 2016-2020, Arvid Norberg +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 SESSION_VIEW_HPP_ +#define SESSION_VIEW_HPP_ + +#include +#include + +#include "libtorrent/session_stats.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/time.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, lt::clock_type::time_point t); + +private: + + int m_position = 0; + int m_width = 80; + + // there are two sets of counters. the current one and the last one. This + // is used to calculate rates + std::vector m_cnt[2]; + + std::int64_t value(int idx) const; + std::int64_t prev_value(int idx) const; + + // the timestamps of the counters in m_cnt[0] and m_cnt[1] + // respectively. + lt::clock_type::time_point 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_num_blocks_read_idx = lt::find_metric_idx("disk.num_blocks_read"); + 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_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..0755bd9 --- /dev/null +++ b/examples/simple_client.cpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2003, 2005, 2009, 2015-2017, 2019-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 +#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..9f21c42 --- /dev/null +++ b/examples/stats_counters.cpp @@ -0,0 +1,50 @@ +/* + +Copyright (c) 2010, 2014-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 "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/torrent2magnet.cpp b/examples/torrent2magnet.cpp new file mode 100644 index 0000000..1cd397d --- /dev/null +++ b/examples/torrent2magnet.cpp @@ -0,0 +1,94 @@ +/* + +Copyright (c) 2019-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 + +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/span.hpp" + +namespace { + +[[noreturn]] void print_usage() +{ + std::cerr << R"(usage: torrent2magnet torrent-file [options] + OPTIONS: + --no-trackers do not include trackers in the magnet link + --no-web-seeds do not include web seeds in the magnet link +)"; + std::exit(1); +} + +} // anonymous namespace + +int main(int argc, char const* argv[]) try +{ + lt::span args(argv, argc); + + // strip executable name + args = args.subspan(1); + + if (args.empty()) print_usage(); + + char const* filename = args[0]; + args = args.subspan(1); + + lt::load_torrent_limits cfg; + lt::torrent_info t(filename, cfg); + + using namespace lt::literals; + + while (!args.empty()) + { + if (args[0] == "--no-trackers"_sv) + { + t.clear_trackers(); + } + else if (args[0] == "--no-web-seeds"_sv) + { + t.set_web_seeds({}); + } + else + { + std::cerr << "unknown option: " << args[0] << "\n"; + print_usage(); + } + args = args.subspan(1); + } + + std::cout << lt::make_magnet_uri(t) << '\n'; + return 0; +} +catch (std::exception const& e) +{ + std::cerr << "ERROR: " << e.what() << "\n"; +} diff --git a/examples/torrent_view.cpp b/examples/torrent_view.cpp new file mode 100644 index 0000000..f442081 --- /dev/null +++ b/examples/torrent_view.cpp @@ -0,0 +1,481 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, AllSeeingEyeTolledEweSew +Copyright (c) 2018, 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 "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 + +namespace { + +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", "", "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.state == lt::torrent_status::downloading + && (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.0); + 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_hashes < rhs->info_hashes; + } + + return (lhs->queue_position == queue_position_t{-1}) + < (rhs->queue_position == queue_position_t{-1}); +} + +} + +torrent_view::torrent_view() = default; + +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[std::size_t(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[std::size_t(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[std::size_t(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[std::size_t(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[std::size_t(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[std::size_t(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(), std::size_t(dest.size()), "%s[%s]%s" + , m_torrent_filter == i?esc("7"):"" + , filter_names[std::size_t(i)], m_torrent_filter == i?esc("0"):""); + if (ret >= 0 && ret <= dest.size()) dest = dest.subspan(ret); + } + int const ret = std::snprintf(dest.data(), std::size_t(dest.size()), "\x1b[K"); + if (ret >= 0 && ret <= dest.size()) dest = dest.subspan(ret); + + if (m_width + 1 < int(str.size())) + str.back() = '\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.back() = '\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(), std::size_t(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(), std::size_t(dest.size()), "%s", esc("0")); + if (ret2 >= 0 && ret2 <= dest.size()) dest = dest.subspan(ret2); + } + + int const ret2 = std::snprintf(dest.data(), std::size_t(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..5569147 --- /dev/null +++ b/examples/torrent_view.hpp @@ -0,0 +1,112 @@ +/* + +Copyright (c) 2014-2015, 2017-2020, Arvid Norberg +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_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 = 0; // index into m_filtered_handles + int m_scroll_position = 0; + int m_torrent_filter = 0; + int m_width = 80; + int m_height = 30; +}; + +#endif // TORRENT_VIEW_HPP_ + diff --git a/examples/upnp_test.cpp b/examples/upnp_test.cpp new file mode 100644 index 0000000..2a48b98 --- /dev/null +++ b/examples/upnp_test.cpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2010, 2014-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 +#include "libtorrent/session.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/alert_types.hpp" + +namespace { +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\n", 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/fuzzers/Jamfile b/fuzzers/Jamfile new file mode 100644 index 0000000..716ce55 --- /dev/null +++ b/fuzzers/Jamfile @@ -0,0 +1,102 @@ +# to fuzz libtorrent, you need a recent version of clang. + +# if you have a favourite component to fuzz, you can run that specific binary +# without specifying the "-runs=" argument, it's probably a good idea to seed +# the fuzzing with the included corpus though + +import feature : feature ; + +use-project /torrent : .. ; + +feature fuzz : off external on : composite propagated link-incompatible ; +feature.compose on : -fsanitize=fuzzer -fsanitize=fuzzer ; + +feature sanitize : off on : composite propagated link-incompatible ; +feature.compose on : norecover norecover ; + +# this is a build configuration that only does limited validation (i.e. no +# sanitizers, invariant-checks, asserts etc.). The purpose is to quickly iterate +# on inputs to build code coverage +variant build_coverage : release : off on off off ; + +project fuzzers + : requirements + on + TORRENT_USE_IPV6=1 + _SCL_SECURE=1 + _GLIBCXX_DEBUG + -fno-omit-frame-pointer + -fno-omit-frame-pointer + /torrent//torrent + : default-build + 14 + on + multi + on + static + release + on + on + on + on + on + ; + +local TARGETS ; + +rule fuzzer ( name ) +{ + exe $(name) : src/$(name).cpp : off:main.cpp ; + TARGETS += $(name) ; +} + +fuzzer torrent_info ; +fuzzer parse_magnet_uri ; +fuzzer bdecode_node ; +fuzzer parse_int ; +fuzzer sanitize_path ; +fuzzer escape_path ; +fuzzer file_storage_add_file ; +fuzzer base32decode ; +fuzzer base32encode ; +fuzzer base64encode ; +fuzzer escape_string ; +fuzzer gzip ; +fuzzer verify_encoding ; +fuzzer convert_to_native ; +fuzzer convert_from_native ; +fuzzer utf8_codepoint ; +fuzzer http_parser ; +fuzzer upnp ; +fuzzer dht_node ; +fuzzer utp ; +fuzzer resume_data ; +fuzzer peer_conn ; +fuzzer idna ; +fuzzer parse_url ; +fuzzer http_tracker ; +fuzzer session_params ; +fuzzer add_torrent ; + +local LARGE_TARGETS = + torrent_info + bdecode_node + http_parser + dht_node + utp + resume_data + file_storage_add_file + sanitize_path + upnp + peer_conn + http_tracker + session_params + add_torrent + ; + +install stage : $(TARGETS) : EXE fuzzers ; +install stage-large : $(LARGE_TARGETS) : EXE fuzzers ; + +explicit stage ; +explicit stage-large ; + diff --git a/fuzzers/LICENSE b/fuzzers/LICENSE new file mode 100644 index 0000000..8778d7c --- /dev/null +++ b/fuzzers/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +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 copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (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/fuzzers/README.rst b/fuzzers/README.rst new file mode 100644 index 0000000..f973b1f --- /dev/null +++ b/fuzzers/README.rst @@ -0,0 +1,64 @@ +libtorrent fuzzing +================== + +Fuzzing of various libtorrent APIs (both internal and external), +inspired by Kostya Serebryany's `cppcon 2017 presentation`_ + +This project requires: + +.. _`cppcon 2017 presentation`: https://www.youtube.com/watch?v=k-Cv8Q3zWNQ&index=36&list=PLHTh1InhhwT6bwIpRk0ZbCA0N2p1taxd6 + +clang +..... + +A very recent version of clang that supports libFuzzer. +clang-5.0 may not be recent enough, you may have to build head from source. + +boost-build +........... + +Also known as ``b2``. To configure boost build with your fresh clang build, +create a ``~/user-config.jam`` with something like this in it (example for macOS):: + + using darwin : 6.0 : ~/Documents/dev/clang/build/bin/clang++ ; + +Or on Linux:: + + using clang ; + +corpus +...... + +The corpus is the set of inputs that has been built by libFuzzer. It's the seed +for testing more mutations. The corpus is not checked into the repository, +before running the fuzzer it is advised to download and unzip the corpus +associated with the latest release on github. + + https://github.com/arvidn/libtorrent/releases/download/libtorrent_1_2_0/corpus.zip + +Uzip the corpus in the fuzzers directory:: + + unzip corpus.zip + +building +........ + +To build the fuzzers:: + + b2 clang stage + +The fuzzers binaries are placed in a directory called `fuzzers`. + +running +....... + +To run the fuzzers, there's a convenience `run.sh` script that launches all +fuzzers in parallel. By default, each fuzzer runs for 48 hours. This can be +adjusted in the `run.sh` script. + +contribute +.......... + +Please consider contributing back any updated corpuses (amended by more seed +inputs) or fuzzers for more APIs in libtorrent. + diff --git a/fuzzers/main.cpp b/fuzzers/main.cpp new file mode 100644 index 0000000..1d5d334 --- /dev/null +++ b/fuzzers/main.cpp @@ -0,0 +1,56 @@ +/* + +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 +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const*, size_t); + +int main(int const argc, char const** argv) +{ + if (argc < 2) + { + std::cout << "usage: " << argv[0] << " test-case-file\n"; + return 1; + } + + std::fstream f(argv[1], std::ios_base::in | std::ios_base::binary); + f.seekg(0, std::ios_base::end); + auto const s = f.tellg(); + f.seekg(0, std::ios_base::beg); + std::vector v(static_cast(s)); + f.read(reinterpret_cast(v.data()), v.size()); + + return LLVMFuzzerTestOneInput(v.data(), v.size()); +} + diff --git a/fuzzers/minimize.sh b/fuzzers/minimize.sh new file mode 100755 index 0000000..4141dbe --- /dev/null +++ b/fuzzers/minimize.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e + +function minimize +{ +mkdir corpus/${1} +./fuzzers/${1} -artifact_prefix=./${1}- -merge=1 corpus/${1} prev-corpus/${1} +} + +mv corpus prev-corpus +mkdir corpus + +for file in fuzzers/*; do + minimize $(basename $file) & +done + +wait + diff --git a/fuzzers/run.sh b/fuzzers/run.sh new file mode 100755 index 0000000..d4bf924 --- /dev/null +++ b/fuzzers/run.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +function run +{ +# run for 48 hours +nice ./fuzzers/${1} -max_total_time=172800 -timeout=10 -artifact_prefix=./${1}- corpus/${1} +} + +for file in fuzzers/*; do + run $(basename $file) & +done + +wait diff --git a/fuzzers/src/add_torrent.cpp b/fuzzers/src/add_torrent.cpp new file mode 100644 index 0000000..416578f --- /dev/null +++ b/fuzzers/src/add_torrent.cpp @@ -0,0 +1,243 @@ +/* + +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 +#include +#include + +#include "libtorrent/session.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/disk_interface.hpp" // for default_block_size +#include "libtorrent/aux_/merkle.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/disabled_disk_io.hpp" +#include "read_bits.hpp" + +using namespace lt; + +lt::session_params g_params; +io_context g_ioc; +std::shared_ptr g_torrent; +std::vector g_tree; + +int const piece_size = 1024 * 1024; +int const blocks_per_piece = piece_size / lt::default_block_size; +int const num_pieces = 10; +int const num_leafs = merkle_num_leafs(num_pieces * blocks_per_piece); +int const num_nodes = merkle_num_nodes(num_leafs); +int const first_leaf = merkle_first_leaf(num_leafs); + +extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + lt::settings_pack& pack = g_params.settings; + // set up settings pack we'll be using + pack.set_int(settings_pack::tick_interval, 1); + pack.set_int(settings_pack::alert_mask, 0); + 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_int(settings_pack::aio_threads, 0); + + // don't waste time making outbound connections + pack.set_bool(settings_pack::enable_outgoing_tcp, false); + pack.set_bool(settings_pack::enable_outgoing_utp, false); + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_dht, false); + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_ip_notifier, false); + pack.set_str(settings_pack::listen_interfaces, "127.0.0.1:0"); + + g_params.disk_io_constructor = lt::disabled_disk_io_constructor; + + // create a torrent + file_storage fs; + std::int64_t const total_size = std::int64_t(piece_size) * num_pieces; + fs.add_file("test_file", total_size); + + g_tree.resize(num_nodes); + + create_torrent t(fs, piece_size); + + std::vector piece(piece_size, 0); + lt::span piece_span(piece); + std::vector piece_tree(merkle_num_nodes(blocks_per_piece)); + for (piece_index_t i : fs.piece_range()) + { + std::memset(piece.data(), char(static_cast(i) & 0xff), piece.size()); + t.set_hash(piece_index_t(i), lt::hasher(piece).final()); + + for (int k = 0; k < blocks_per_piece; ++k) + { + auto const h = lt::hasher256(piece_span.subspan( + k * lt::default_block_size, lt::default_block_size)).final(); + piece_tree[std::size_t(k)] = h; + g_tree[std::size_t(first_leaf + static_cast(i) * blocks_per_piece + k)] = h; + } + + auto const r = merkle_root(piece_tree); + t.set_hash2(file_index_t{0}, piece_index_t::diff_type(i), r); + } + + merkle_fill_tree(g_tree, num_leafs); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + g_torrent = std::make_shared(buf, from_span); + + return 0; +} + +lt::add_torrent_params generate_atp(std::uint8_t const* data, size_t size) +{ + read_bits bits(data, size); + lt::add_torrent_params ret; + ret.ti = g_torrent; + ret.info_hashes = g_torrent->info_hashes(); + ret.save_path = "."; + ret.file_priorities.resize(bits.read(2)); + for (auto& p : ret.file_priorities) + p = lt::download_priority_t(bits.read(3)); + ret.flags = lt::torrent_flags_t(bits.read(24)); + int const num_unfinished = bits.read(4); + for (int i = 0; i < num_unfinished; ++i) + { + auto& mask = ret.unfinished_pieces[piece_index_t(bits.read(32))]; + mask.resize(bits.read(5)); + for (int i = 0; i < mask.size(); ++i) + if (bits.read(1)) mask.set_bit(i); else mask.set_bit(i); + } + ret.have_pieces.resize(bits.read(6)); + for (int i = 0; i < ret.have_pieces.size(); ++i) + if (bits.read(1)) ret.have_pieces.set_bit(i); else ret.have_pieces.set_bit(i); + + ret.verified_pieces.resize(bits.read(6)); + for (int i = 0; i < ret.verified_pieces.size(); ++i) + if (bits.read(1)) ret.verified_pieces.set_bit(i); else ret.verified_pieces.set_bit(i); + + ret.piece_priorities.resize(bits.read(6)); + for (auto& p : ret.piece_priorities) + p = lt::download_priority_t(bits.read(1)); + + // if we read a 1 here, initialize the merkle tree fields correctly + if (bits.read(1)) + { + ret.merkle_trees.resize(1); + ret.merkle_tree_mask.resize(1); + ret.verified_leaf_hashes.resize(1); + ret.verified_leaf_hashes[0].resize(num_leafs, true); + + auto& t = ret.merkle_trees[0]; + auto& mask = ret.merkle_tree_mask[0]; + mask.resize(num_nodes, false); + int idx = -1; + for (auto const& h : g_tree) + { + ++idx; + if (h.is_all_zeros()) continue; + mask[std::size_t(idx)] = true; + t.push_back(g_tree[std::size_t(idx)]); + } + } + else + { + ret.merkle_trees.resize(bits.read(2)); + for (auto& t : ret.merkle_trees) + { + std::size_t block = 0; + t.resize(bits.read(13)); + for (auto& h : t) + { + h = g_tree[block++]; + if (block >= g_tree.size()) block = 0; + } + } + ret.merkle_tree_mask.resize(bits.read(2)); + for (auto& m : ret.merkle_tree_mask) + { + m.resize(bits.read(13)); + for (std::size_t i = 0; i < m.size(); ++i) + m[i] = bits.read(1); + } + ret.verified_leaf_hashes.resize(bits.read(2)); + for (auto& m : ret.verified_leaf_hashes) + { + m.resize(bits.read(4)); + for (std::size_t i = 0; i < m.size(); ++i) + m[i] = bits.read(1); + } + } + + ret.max_uploads = bits.read(32); + ret.max_connections = bits.read(32); + ret.upload_limit = bits.read(32); + ret.download_limit = bits.read(32); + ret.active_time = bits.read(32); + ret.finished_time = bits.read(32); + ret.seeding_time = bits.read(32); + ret.last_seen_complete = bits.read(32); + ret.num_complete = bits.read(32); + ret.num_incomplete = bits.read(32); + ret.num_downloaded = bits.read(32); + + return ret; +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + g_ioc.restart(); + boost::optional ses(lt::session{g_params, g_ioc}); + + lt::add_torrent_params atp = generate_atp(data, size); + + ses->async_add_torrent(atp); + auto proxy = ses->abort(); + post(g_ioc, [&]{ ses.reset(); }); + + g_ioc.run_for(seconds(2)); + +#if defined TORRENT_ASIO_DEBUGGING + lt::log_async(); + lt::_async_ops.clear(); + lt::_async_ops_nthreads = 0; + lt::_wakeups.clear(); +#endif + + return 0; +} + + diff --git a/fuzzers/src/base32decode.cpp b/fuzzers/src/base32decode.cpp new file mode 100644 index 0000000..1004de8 --- /dev/null +++ b/fuzzers/src/base32decode.cpp @@ -0,0 +1,40 @@ +/* + +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 "libtorrent/aux_/escape_string.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::base32decode({reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/base32encode.cpp b/fuzzers/src/base32encode.cpp new file mode 100644 index 0000000..737e52b --- /dev/null +++ b/fuzzers/src/base32encode.cpp @@ -0,0 +1,40 @@ +/* + +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 "libtorrent/aux_/escape_string.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::base32encode({reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/base64encode.cpp b/fuzzers/src/base64encode.cpp new file mode 100644 index 0000000..2ce2c12 --- /dev/null +++ b/fuzzers/src/base64encode.cpp @@ -0,0 +1,40 @@ +/* + +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 "libtorrent/aux_/escape_string.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::base64encode({reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/bdecode_node.cpp b/fuzzers/src/bdecode_node.cpp new file mode 100644 index 0000000..2578dfd --- /dev/null +++ b/fuzzers/src/bdecode_node.cpp @@ -0,0 +1,41 @@ +/* + +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 "libtorrent/bdecode.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::error_code ec; + lt::bdecode({reinterpret_cast(data), int(size)}, ec); + return 0; +} + diff --git a/fuzzers/src/convert_from_native.cpp b/fuzzers/src/convert_from_native.cpp new file mode 100644 index 0000000..0aa7eab --- /dev/null +++ b/fuzzers/src/convert_from_native.cpp @@ -0,0 +1,40 @@ +/* + +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 "libtorrent/aux_/escape_string.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::convert_from_native({reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/convert_to_native.cpp b/fuzzers/src/convert_to_native.cpp new file mode 100644 index 0000000..d03d1e8 --- /dev/null +++ b/fuzzers/src/convert_to_native.cpp @@ -0,0 +1,40 @@ +/* + +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 "libtorrent/aux_/escape_string.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::convert_to_native({reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/dht_node.cpp b/fuzzers/src/dht_node.cpp new file mode 100644 index 0000000..97d1edc --- /dev/null +++ b/fuzzers/src/dht_node.cpp @@ -0,0 +1,102 @@ +/* + +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 "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/aux_/session_impl.hpp" + +#include + +using namespace lt; + +aux::session_settings sett; +dht::dht_state state; +std::unique_ptr dht_storage(dht::dht_default_storage_constructor(sett)); + +counters cnt; + +struct obs : dht::dht_observer +{ + void set_external_address(lt::aux::listen_socket_handle const&, lt::address const& /* addr */ + , lt::address const&) override + {} + int get_listen_port(aux::transport ssl, aux::listen_socket_handle const& s) override + { return 6881; } + + void get_peers(lt::sha1_hash const&) override {} + void outgoing_get_peers(sha1_hash const& + , sha1_hash const&, lt::udp::endpoint const&) override {} + void announce(sha1_hash const&, lt::address const&, int) override {} + bool on_dht_request(string_view + , dht::msg const&, entry&) override + { return false; } + +#ifndef TORRENT_DISABLE_LOGGING + + void log(dht_logger::module_t, char const*, ...) override {} + + bool should_log(module_t) const override { return true; } + void log_packet(message_direction_t + , span + , lt::udp::endpoint const&) override {} +#endif // TORRENT_DISABLE_LOGGING +}; + +obs o; +io_context ios; +dht::dht_tracker dht_node(&o + , ios + , [](aux::listen_socket_handle const&, udp::endpoint const& + , span, error_code&, udp_send_flags_t) {} + , sett + , cnt + , *dht_storage + , std::move(state)); +auto listen_socket = std::make_shared(); +aux::listen_socket_handle s(listen_socket); + +error_code ignore; +lt::address_v4 src = make_address_v4("2.2.2.2", ignore); +udp::endpoint ep(src, 6881); +std::once_flag once_flag; + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + ep.address(src); + src = lt::address_v4(aux::plus_one(src.to_bytes())); + std::call_once(once_flag, []{ dht_node.new_socket(s); }); + dht_node.incoming_packet(s, ep, {reinterpret_cast(data), int(size)}); + return 0; +} + diff --git a/fuzzers/src/escape_path.cpp b/fuzzers/src/escape_path.cpp new file mode 100644 index 0000000..87b4426 --- /dev/null +++ b/fuzzers/src/escape_path.cpp @@ -0,0 +1,40 @@ +/* + +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 "libtorrent/aux_/escape_string.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::escape_path({reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/escape_string.cpp b/fuzzers/src/escape_string.cpp new file mode 100644 index 0000000..d6bd218 --- /dev/null +++ b/fuzzers/src/escape_string.cpp @@ -0,0 +1,40 @@ +/* + +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 "libtorrent/aux_/escape_string.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::escape_string({reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/file_storage_add_file.cpp b/fuzzers/src/file_storage_add_file.cpp new file mode 100644 index 0000000..7151c9b --- /dev/null +++ b/fuzzers/src/file_storage_add_file.cpp @@ -0,0 +1,45 @@ +/* + +Copyright (c) 2019-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/file_storage.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::file_storage fs; + // we expect this call to fail sometimes + try { + fs.add_file({reinterpret_cast(data), size}, 1); + } + catch (...) {} + return 0; +} + diff --git a/fuzzers/src/gzip.cpp b/fuzzers/src/gzip.cpp new file mode 100644 index 0000000..93324e4 --- /dev/null +++ b/fuzzers/src/gzip.cpp @@ -0,0 +1,43 @@ +/* + +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 "libtorrent/gzip.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::error_code ec; + std::vector out; + lt::inflate_gzip({reinterpret_cast(data), int(size)}, out + , 100000, ec); + return 0; +} + diff --git a/fuzzers/src/http_parser.cpp b/fuzzers/src/http_parser.cpp new file mode 100644 index 0000000..35744f9 --- /dev/null +++ b/fuzzers/src/http_parser.cpp @@ -0,0 +1,60 @@ +/* + +Copyright (c) 2019-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/http_parser.hpp" +#include "libtorrent/string_view.hpp" + +void feed_bytes(lt::http_parser& parser, lt::string_view str) +{ + for (int chunks = 1; chunks < 70; ++chunks) + { + parser.reset(); + lt::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)); + bool error = false; + parser.incoming(recv_buf, error); + if (error) break; + } + } +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::http_parser p; + feed_bytes(p, {reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/http_tracker.cpp b/fuzzers/src/http_tracker.cpp new file mode 100644 index 0000000..a74adee --- /dev/null +++ b/fuzzers/src/http_tracker.cpp @@ -0,0 +1,49 @@ +/* + +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/http_tracker_connection.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::error_code ec; + lt::sha1_hash const ih("abababababababababab"); + lt::span const input(reinterpret_cast(data), size); + + parse_tracker_response(input, ec, lt::tracker_request_flags_t{}, ih); + parse_tracker_response(input, ec, lt::tracker_request::scrape_request, ih); +#if TORRENT_USE_I2P + parse_tracker_response(input, ec, lt::tracker_request::i2p, ih); +#endif + + return 0; +} + diff --git a/fuzzers/src/idna.cpp b/fuzzers/src/idna.cpp new file mode 100644 index 0000000..83ca051 --- /dev/null +++ b/fuzzers/src/idna.cpp @@ -0,0 +1,42 @@ +/* + +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/parse_url.hpp" +#include "libtorrent/string_view.hpp" + +#include + +extern "C" int LLVMFuzzerTestOneInput(std::uint8_t const* data, size_t size) +{ + lt::is_idna(lt::string_view(reinterpret_cast(data), size)); + return 0; +} diff --git a/fuzzers/src/parse_int.cpp b/fuzzers/src/parse_int.cpp new file mode 100644 index 0000000..ca57076 --- /dev/null +++ b/fuzzers/src/parse_int.cpp @@ -0,0 +1,41 @@ +/* + +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 "libtorrent/bdecode.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::bdecode_errors::error_code_enum ec; + std::int64_t val = 0; + lt::parse_int(reinterpret_cast(data), reinterpret_cast(data) + size, ':', val, ec); + return 0; +} + diff --git a/fuzzers/src/parse_magnet_uri.cpp b/fuzzers/src/parse_magnet_uri.cpp new file mode 100644 index 0000000..86b96fc --- /dev/null +++ b/fuzzers/src/parse_magnet_uri.cpp @@ -0,0 +1,45 @@ +/* + +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 "libtorrent/magnet_uri.hpp" +#include "libtorrent/add_torrent_params.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::error_code ec; + lt::add_torrent_params params; + lt::parse_magnet_uri({reinterpret_cast(data), size} + , params, ec); + return 0; +} + + diff --git a/fuzzers/src/parse_url.cpp b/fuzzers/src/parse_url.cpp new file mode 100644 index 0000000..b376f69 --- /dev/null +++ b/fuzzers/src/parse_url.cpp @@ -0,0 +1,43 @@ +/* + +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/parse_url.hpp" + +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(std::uint8_t const* data, size_t size) +{ + lt::error_code ec; + lt::parse_url_components(std::string(reinterpret_cast(data), size), ec); + return 0; +} diff --git a/fuzzers/src/peer_conn.cpp b/fuzzers/src/peer_conn.cpp new file mode 100644 index 0000000..a7a9a9e --- /dev/null +++ b/fuzzers/src/peer_conn.cpp @@ -0,0 +1,222 @@ +/* + +Copyright (c) 2019-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 +#include +#include "libtorrent/session.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/random.hpp" + +#include "libtorrent/io_context.hpp" + +using namespace lt; + +std::unique_ptr g_ses; +info_hash_t g_info_hash; +int g_listen_port = 0; +io_context g_ios; + +//#define DEBUG_LOGGING 1 + +extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + // set up a session + settings_pack pack; + pack.set_int(settings_pack::piece_timeout, 1); + pack.set_int(settings_pack::request_timeout, 1); + pack.set_int(settings_pack::peer_timeout, 1); + pack.set_int(settings_pack::peer_connect_timeout, 1); + pack.set_int(settings_pack::inactivity_timeout, 1); + pack.set_int(settings_pack::handshake_timeout, 1); + +#ifdef DEBUG_LOGGING + pack.set_int(settings_pack::alert_mask, 0xffffff); +#else + pack.set_int(settings_pack::alert_mask, alert_category::connect + | alert_category::error + | alert_category::status + | alert_category::peer); +#endif + + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); + + // don't waste time making outbound connections + pack.set_bool(settings_pack::enable_outgoing_tcp, false); + pack.set_bool(settings_pack::enable_outgoing_utp, false); + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_dht, false); + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_ip_notifier, false); + + // pick an available listen port and only listen on loopback + pack.set_str(settings_pack::listen_interfaces, "127.0.0.1:0"); + + g_ses = std::unique_ptr(new lt::session(pack)); + + // create a torrent + file_storage fs; + int const piece_size = 1024 * 1024; + std::int64_t const total_size = std::int64_t(piece_size) * 100; + fs.add_file("test_file", total_size); + + create_torrent t(fs, piece_size); + + for (piece_index_t i : fs.piece_range()) + t.set_hash(i, sha1_hash("abababababababababab")); + + for (file_index_t const f : fs.file_range()) + for (piece_index_t::diff_type i : fs.file_piece_range(f)) + t.set_hash2(f, i, sha256_hash("abababababababababababababababababababababababababababababababab")); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + auto ti = std::make_shared(buf, from_span); + + // remember the info-hash to give the fuzzer a chance to connect to it + g_info_hash = ti->info_hashes(); + + // add the torrent to the session + add_torrent_params atp; + atp.ti = std::move(ti); + atp.save_path = "."; + + g_ses->add_torrent(std::move(atp)); + + // pull the alerts for the listen socket we ended up using + time_point const end_time = clock_type::now() + seconds(5); + bool started = false; + while (g_listen_port == 0 || !started) + { + std::vector alerts; + auto const now = clock_type::now(); + if (now > end_time) return -1; + + g_ses->wait_for_alert(end_time - now); + g_ses->pop_alerts(&alerts); + + for (auto const a : alerts) + { + std::cout << a->message() << '\n'; + if (auto la = alert_cast(a)) + { + if (la->socket_type == socket_type_t::tcp) + { + g_listen_port = la->port; + std::cout << "listening on " << g_listen_port << '\n'; + } + } + if (alert_cast(a)) + { + started = true; + } + } + } + + // we have to destruct the session before global destructors, such as the + // system error code category. The session objects rely on error_code during + // its destruction + std::atexit([]{ g_ses.reset(); }); + + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + if (size < 8) return 0; + +#ifdef DEBUG_LOGGING + time_point const start_time = clock_type::now(); +#endif + // connect + tcp::socket s(g_ios); + error_code ec; + do { + ec.clear(); + error_code ignore; + s.connect(tcp::endpoint(make_address("127.0.0.1", ignore), g_listen_port), ec); + } while (ec == boost::system::errc::interrupted); + + // bittorrent handshake + + std::vector handshake(1 + 19 + 8 + 20 + 20 + size - 8); + std::memcpy(handshake.data(), "\x13" "BitTorrent protocol", 20); + std::memcpy(handshake.data() + 20, data, 8); + std::memcpy(handshake.data() + 28, g_info_hash.get_best().data(), 20); + lt::aux::random_bytes({handshake.data() + 48, 20}); + data += 8; + size -= 8; + std::memcpy(handshake.data() + 68, data, size); + + // we're likely to fail to write entire (garbage) messages, as libtorrent may + // disconnect us half-way through. This may fail with broken_pipe for + // instance + error_code ignore; + boost::asio::write(s, boost::asio::buffer(handshake), ignore); + + s.close(); + + // wait for the alert saying the connection was closed + + time_point const end_time = clock_type::now() + seconds(3); + for (;;) + { + std::vector alerts; + auto const now = clock_type::now(); + if (now > end_time) return -1; + + g_ses->wait_for_alert(end_time - now); + g_ses->pop_alerts(&alerts); + + for (auto const a : alerts) + { +#ifdef DEBUG_LOGGING + std::cout << duration_cast(a->timestamp() - start_time).count() + << ": " << a->message() << '\n'; +#endif + if (alert_cast(a) + || alert_cast(a)) + { + goto done; + } + } + } +done: + + return 0; +} + diff --git a/fuzzers/src/read_bits.hpp b/fuzzers/src/read_bits.hpp new file mode 100644 index 0000000..9e08fd6 --- /dev/null +++ b/fuzzers/src/read_bits.hpp @@ -0,0 +1,65 @@ +/* + +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. + +*/ + +struct read_bits +{ + read_bits(std::uint8_t const* d, std::size_t s) + : m_data(d), m_size(s) + {} + + int read(int bits) + { + if (m_size == 0) return 0; + int ret = 0; + while (bits > 0 && m_size > 0) + { + int const bits_to_copy = std::min(8 - m_bit, bits); + ret <<= bits_to_copy; + ret |= ((*m_data) >> m_bit) & ((1 << bits_to_copy) - 1); + m_bit += bits_to_copy; + bits -= bits_to_copy; + if (m_bit == 8) + { + --m_size; + ++m_data; + m_bit = 0; + } + } + return ret; + } +private: + std::uint8_t const* m_data; + std::size_t m_size; + int m_bit = 0; +}; + + diff --git a/fuzzers/src/resume_data.cpp b/fuzzers/src/resume_data.cpp new file mode 100644 index 0000000..dd02d4c --- /dev/null +++ b/fuzzers/src/resume_data.cpp @@ -0,0 +1,45 @@ +/* + +Copyright (c) 2019-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 +#include "libtorrent/read_resume_data.hpp" +#include "libtorrent/write_resume_data.hpp" +#include "libtorrent/add_torrent_params.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::error_code ec; + auto ret = lt::read_resume_data({reinterpret_cast(data), int(size)}, ec); + auto buf = write_resume_data_buf(ret); + return 0; +} + diff --git a/fuzzers/src/sanitize_path.cpp b/fuzzers/src/sanitize_path.cpp new file mode 100644 index 0000000..baaada9 --- /dev/null +++ b/fuzzers/src/sanitize_path.cpp @@ -0,0 +1,41 @@ +/* + +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 "libtorrent/torrent_info.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + std::string out; + lt::aux::sanitize_append_path_element(out, {reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/session_params.cpp b/fuzzers/src/session_params.cpp new file mode 100644 index 0000000..360eab7 --- /dev/null +++ b/fuzzers/src/session_params.cpp @@ -0,0 +1,44 @@ +/* + +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/session_params.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + try { + auto ret = lt::read_session_params({reinterpret_cast(data), int(size)}); + } catch (...) {} + return 0; +} + + diff --git a/fuzzers/src/torrent_info.cpp b/fuzzers/src/torrent_info.cpp new file mode 100644 index 0000000..7ff3c13 --- /dev/null +++ b/fuzzers/src/torrent_info.cpp @@ -0,0 +1,41 @@ +/* + +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 "libtorrent/torrent_info.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::error_code ec; + lt::torrent_info ti({reinterpret_cast(data), int(size)}, ec, lt::from_span); + return 0; +} + diff --git a/fuzzers/src/upnp.cpp b/fuzzers/src/upnp.cpp new file mode 100644 index 0000000..ffebb61 --- /dev/null +++ b/fuzzers/src/upnp.cpp @@ -0,0 +1,45 @@ +/* + +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 "libtorrent/upnp.hpp" +#include "libtorrent/xml_parse.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + using namespace std::placeholders; + + lt::parse_state s; + lt::xml_parse({reinterpret_cast(data), size} + , std::bind(<::find_control_url, _1, _2, std::ref(s))); + return 0; +} + diff --git a/fuzzers/src/utf8_codepoint.cpp b/fuzzers/src/utf8_codepoint.cpp new file mode 100644 index 0000000..72f5450 --- /dev/null +++ b/fuzzers/src/utf8_codepoint.cpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2019-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/utf8.hpp" + +#include + +extern "C" int LLVMFuzzerTestOneInput(std::uint8_t const* data, size_t size) +{ + if (size == 0) return 0; + lt::parse_utf8_codepoint({reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/utp.cpp b/fuzzers/src/utp.cpp new file mode 100644 index 0000000..2c8c9a8 --- /dev/null +++ b/fuzzers/src/utp.cpp @@ -0,0 +1,70 @@ +/* + +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 "libtorrent/aux_/utp_socket_manager.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/aux_/utp_stream.hpp" +#include "libtorrent/udp_socket.hpp" + +using namespace lt; + +io_context ios; +lt::aux::session_settings sett; +counters cnt; + +aux::utp_socket_manager man( + [](std::weak_ptr, udp::endpoint const&, span, error_code&, udp_send_flags_t){} + , [](aux::socket_type){} + , ios + , sett + , cnt + , nullptr); + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + std::unique_ptr sock; + { + aux::utp_stream str(ios); + sock = std::make_unique(1, 0, &str, man); + str.set_impl(sock.get()); + udp::endpoint ep; + time_point ts(seconds(100)); + span buf(reinterpret_cast(data), size); + sock->incoming_packet(buf, ep, ts); + + // clear any deferred acks + man.socket_drained(); + } + return 0; +} + diff --git a/fuzzers/src/verify_encoding.cpp b/fuzzers/src/verify_encoding.cpp new file mode 100644 index 0000000..5104531 --- /dev/null +++ b/fuzzers/src/verify_encoding.cpp @@ -0,0 +1,42 @@ +/* + +Copyright (c) 2019-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/torrent_info.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + if (size == 0) return 0; + std::string str{reinterpret_cast(data), size}; + lt::aux::verify_encoding(str); + return 0; +} + diff --git a/fuzzers/tools/generate_initial_corpus.py b/fuzzers/tools/generate_initial_corpus.py new file mode 100644 index 0000000..efc1dc1 --- /dev/null +++ b/fuzzers/tools/generate_initial_corpus.py @@ -0,0 +1,214 @@ +import os +import shutil +import hashlib +import struct +import random + +corpus_dirs = [ + 'torrent_info', 'upnp', 'gzip', 'base32decode', 'base32encode', + 'base64encode', 'bdecode_node', 'convert_from_native', 'convert_to_native', + 'dht_node', 'escape_path', 'escape_string', 'file_storage_add_file', + 'http_parser', 'lazy_bdecode', 'parse_int', 'parse_magnet_uri', 'resume_data', + 'sanitize_path', 'utf8_codepoint', 'utp', + 'verify_encoding', 'peer_conn', 'add_torrent', 'idna', 'parse_url', 'http_tracker'] + +for p in corpus_dirs: + try: + os.makedirs(os.path.join('corpus', p)) + except Exception as e: + print(e) + +torrent_dir = '../test/test_torrents' +for f in os.listdir(torrent_dir): + shutil.copy(os.path.join(torrent_dir, f), os.path.join('corpus', 'torrent_info')) + +xml_tests = [ + '', '', '', ' +''', + ''] + +for x in xml_tests: + name = hashlib.sha1(x.encode('ascii')).hexdigest() + with open(os.path.join('corpus', 'upnp', name), 'w+') as f: + f.write(x) + +gzip_dir = '../test' +for f in ['zeroes.gz', 'corrupt.gz', 'invalid1.gz']: + shutil.copy(os.path.join(gzip_dir, f), os.path.join('corpus', 'gzip')) + +idna = ['....', 'xn--foo-.bar', 'foo.xn--bar-.com', 'Xn--foobar-', 'XN--foobar-', '..xnxn--foobar-'] + +counter = 0 +for i in idna: + open(os.path.join('corpus', 'idna', '%d' % counter), 'w+').write(i) + counter += 1 + +urls = ['https://user:password@example.com:8080/path?query'] + +counter = 0 +for i in urls: + open(os.path.join('corpus', 'parse_url', '%d' % counter), 'w+').write(i) + counter += 1 + +counter = 0 +tracker_fields = ['interval', 'min interval', 'tracker id', 'failure reason', + 'warning message', 'complete', 'incomplete', 'downloaded', 'downloaders', 'external ip'] +tracker_values = ['i-1e', 'i0e', 'i1800e', '6:foobar', 'de', '0:', 'le'] +peer_fields = ['peer id', 'ip', 'port'] +peer_values = ['i-1e', 'i0e', 'i1800e', '6:foobar', 'de', '0:', 'le', '9:127.0.0.1'] + +for i in range(1000): + tracker_msg = 'd' + for f in tracker_fields: + tracker_msg += '%d:' % len(f) + f + tracker_msg += random.choice(tracker_values) + + tracker_msg += '5:filesd20:ababababababababababd' + for f in tracker_fields: + tracker_msg += '%d:' % len(f) + f + tracker_msg += random.choice(tracker_values) + tracker_msg += 'ee' + + tracker_msg += '5:peers' + if random.getrandbits(1) == 0: + tracker_msg += 'l' + for k in range(10): + tracker_msg += 'd' + for f in peer_fields: + tracker_msg += '%d:' % len(f) + f + tracker_msg += random.choice(peer_values) + tracker_msg += 'e' + tracker_msg += 'e' + else: + tracker_msg += '60:' + for k in range(6*10): + tracker_msg += chr(random.getrandbits(8)) + + tracker_msg += '6:peers6' + tracker_msg += '180:' + for k in range(18*10): + tracker_msg += chr(random.getrandbits(8)) + + tracker_msg += 'e' + open(os.path.join('corpus', 'http_tracker', '%d' % counter), 'w+').write(tracker_msg) + counter += 1 + +# generate peer protocol messages +messages = [] + + +def add_length(msg): + return struct.pack('>I', len(msg)) + msg + + +def add_reserved(msg): + return b'\0\0\0\0\0\x18\0\x05' + msg + + +# extended handshake +def add_extended_handshake(msg): + ext_handshake = b'd1:md11:ut_metadatai1e11:lt_donthavei2e12:ut_holepunch' + \ + b'i3e11:upload_onlyi4ee11:upload_onlyi1e10:share_modei1e4:reqqi1234e6:yourip4:0000e' + return add_length(struct.pack('BB', 20, 0) + ext_handshake) + msg + + +# request +for i in range(101): + for j in range(-1, 1): + messages.append(add_length(struct.pack('>Biii', 6, i, j, 0x4000))) + +# cancel +for i in range(101): + for j in range(-1, 1): + messages.append(add_length(struct.pack('>Biii', 8, i, j, 0x4000))) + +# piece +for i in range(101): + messages.append(add_length(struct.pack('>Bii', 7, i, 0) + (b'a' * 0x4000))) + +# single-byte +for i in range(256): + messages.append(add_length(struct.pack('B', i))) + +# reject +for i in range(101): + messages.append(add_length(struct.pack('>Biii', 16, i, 0, 0x4000))) + +# suggest +for i in range(101): + messages.append(add_length(struct.pack('>Bi', 13, i))) + +# allow-fast +for i in range(101): + messages.append(add_length(struct.pack('>Bi', 17, i))) + +# have +for i in range(101): + messages.append(add_length(struct.pack('>Bi', 4, i))) + +# DHT-port +for i in range(101): + messages.append(add_length(struct.pack('>BH', 9, i * 10))) + +# hash request +for i in range(-10, 200, 20): + for j in range(-1, 1): + for k in range(-1, 1): + for m in range(-1, 1): + for n in range(-1, 1): + messages.append(add_length(struct.pack('>Biiiii', 21, i, j, k, m, n))) + +# hash reject +for i in range(-10, 200, 20): + for j in range(-1, 1): + for k in range(-1, 1): + for m in range(-1, 1): + for n in range(-1, 1): + messages.append(add_length(struct.pack('>Biiiii', 23, i, j, k, m, n))) + +# hash +for i in range(-10, 200, 20): + for j in range(-1, 1): + messages.append(add_length(struct.pack('>Biiiii', 22, i, j, 0, 2, 0) + (b'0' * 32 * 5))) + +# lt_dont_have +messages.append(add_extended_handshake(add_length(struct.pack('>BBi', 20, 7, -1)))) +messages.append(add_extended_handshake(add_length(struct.pack('>BBi', 20, 7, 0)))) +messages.append(add_extended_handshake(add_length(struct.pack('>BBi', 20, 7, 0x7fffffff)))) + +# share mode +messages.append(add_extended_handshake(add_length(struct.pack('BBB', 20, 8, 255)))) +messages.append(add_extended_handshake(add_length(struct.pack('BBB', 20, 8, 0)))) +messages.append(add_extended_handshake(add_length(struct.pack('BBB', 20, 8, 1)))) + +# holepunch +for i in range(0, 2): + for j in range(0, 1): + messages.append(add_extended_handshake(add_length(struct.pack('>BBBBiH', 20, 4, i, j, 0, 0)))) + messages.append(add_extended_handshake(add_length(struct.pack('>BBBBiiH', 20, 4, i, j, 0, 0, 0)))) + +# upload only +for i in range(0, 1): + messages.append(add_extended_handshake(add_length(struct.pack('BBB', 20, 3, i)))) + +# bitfields +bitfield_len = (100 + 7) // 8 + +for i in range(256): + messages.append(add_length(struct.pack('B', 5) + (struct.pack('B', i) * bitfield_len))) + +mixes = [] + +for i in range(200): + random.shuffle(messages) + mixes.append(b''.join(messages[1:20])) + +messages += mixes + +for m in messages: + f = open('corpus/peer_conn/%s' % hashlib.sha1(m).hexdigest(), 'wb+') + f.write(add_reserved(m)) + f.close() diff --git a/fuzzers/tools/unify_corpus_names.py b/fuzzers/tools/unify_corpus_names.py new file mode 100644 index 0000000..8321d30 --- /dev/null +++ b/fuzzers/tools/unify_corpus_names.py @@ -0,0 +1,24 @@ +import sys +import os +import string +import hashlib + +if len(sys.argv) < 2: + print('usage: unify_corpus_names.py \n') + sys.exit(1) + +root = sys.argv[1] +for name in os.listdir(root): + f = os.path.join(root, name) + + # ignore directories + if not os.path.isfile(f): + continue + + # if the name already looks like a SHA-1 hash, ignore it + if len(name) == 40 and all(c in string.hexdigits for c in name): + continue + + new_name = hashlib.sha1(open(f, 'rb').read()).hexdigest() + print('%s -> %s' % (f, new_name)) + os.rename(f, os.path.join(root, new_name)) diff --git a/include/libtorrent/add_torrent_params.hpp b/include/libtorrent/add_torrent_params.hpp new file mode 100644 index 0000000..11befb5 --- /dev/null +++ b/include/libtorrent/add_torrent_params.hpp @@ -0,0 +1,401 @@ +/* + +Copyright (c) 2009-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2019, ghbplayer +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/info_hash.hpp" +#include "libtorrent/download_priority.hpp" +#include "libtorrent/client_data.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/fwd.hpp" + +namespace libtorrent { + +TORRENT_VERSION_NAMESPACE_3 + + // 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 + { + // hidden + add_torrent_params(); + ~add_torrent_params(); + add_torrent_params(add_torrent_params&&) noexcept; + add_torrent_params& operator=(add_torrent_params&&) &; + 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) \ + TORRENT_DEPRECATED static constexpr torrent_flags_t 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; + + // The ``userdata`` parameter is optional and will be passed on to the + // extension constructor functions, if any + // (see torrent_handle::add_extension()). It will also be stored in the + // torrent object and can be retrieved by calling userdata(). + client_data_t userdata; + + // 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&, client_data_t)>>> + 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; + +#if TORRENT_ABI_VERSION < 3 + // backwards compatible v1 hash, or truncated v2 + TORRENT_DEPRECATED + sha1_hash info_hash; +#endif + + // 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. + info_hash_t info_hashes; + + // ``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 TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // 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. + TORRENT_DEPRECATED aux::noexcept_movable> merkle_tree; +#endif + + // v2 hashes, if known + aux::vector, file_index_t> merkle_trees; + + // if set, indicates which hashes are included in the corresponding + // vector of ``merkle_trees``. These bitmasks always cover the full + // tree, a cleared bit means the hash is all zeros (i.e. not set) and + // set bit means the next hash in the corresponding vector in + // ``merkle_trees`` is the hash for that node. This is an optimization + // to avoid storing a lot of zeros. + aux::vector, file_index_t> merkle_tree_mask; + + // bit-fields indicating which v2 leaf hashes have been verified + // against the root hash. If this vector is empty and merkle_trees is + // non-empty it implies that all hashes in merkle_trees are verified. + aux::vector, file_index_t> verified_leaf_hashes; + + // 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. + TORRENT_DEPRECATED std::string url; + + // 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()``. + TORRENT_DEPRECATED aux::noexcept_movable> 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_3_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..0639c7b --- /dev/null +++ b/include/libtorrent/address.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2006, 2009, 2015, 2017-2020, Arvid Norberg +Copyright (c) 2016, 2018, 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_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 +#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 + + using boost::asio::ip::network_v4; + using boost::asio::ip::make_network_v4; + using boost::asio::ip::v4_mapped; + +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::ip::make_address; + using sim::asio::ip::make_address_v4; + using sim::asio::ip::make_address_v6; +#else + using boost::asio::ip::make_address; + using boost::asio::ip::make_address_v4; + using boost::asio::ip::make_address_v6; +#endif + +} + +#endif diff --git a/include/libtorrent/alert.hpp b/include/libtorrent/alert.hpp new file mode 100644 index 0000000..35c8544 --- /dev/null +++ b/include/libtorrent/alert.hpp @@ -0,0 +1,333 @@ +/* + +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2004-2005, 2008-2009, 2013-2020, Arvid Norberg +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_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 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.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()) + struct TORRENT_EXPORT alert + { +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // hidden + TORRENT_UNEXPORT alert(alert const& rhs) = delete; + alert& operator=(alert const&) = delete; + alert(alert&& rhs) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + 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 + TORRENT_DEPRECATED + static constexpr alert_category_t debug_notification = connect_notification; +#endif + static constexpr alert_category_t status_notification = 6_bit; +#if TORRENT_ABI_VERSION == 1 + // Alerts for when blocks are requested and completed. Also when + // pieces are completed. + TORRENT_DEPRECATED + static constexpr alert_category_t 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; + +#if TORRENT_ABI_VERSION <= 2 + // 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. + TORRENT_DEPRECATED + static constexpr alert_category_t stats_notification = 11_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 + TORRENT_UNEXPORT 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; + + 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_types.hpp b/include/libtorrent/alert_types.hpp new file mode 100644 index 0000000..aa1efa8 --- /dev/null +++ b/include/libtorrent/alert_types.hpp @@ -0,0 +1,2991 @@ +/* + +Copyright (c) 2017, toinetoine +Copyright (c) 2004-2020, Arvid Norberg +Copyright (c) 2008, Andrew Resch +Copyright (c) 2014-2018, Steven Siloti +Copyright (c) 2015, Thomas +Copyright (c) 2015-2018, Alden Torres +Copyright (c) 2017, Antoine Dahan +Copyright (c) 2018, d-komarov +Copyright (c) 2019, ghbplayer +Copyright (c) 2020, Fonic +Copyright (c) 2020, Viktor Elofsson +Copyright (c) 2020, AllSeeingEyeTolledEweSew +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/tracker_manager.hpp" // for event_t +#include "libtorrent/socket_type.hpp" +#include "libtorrent/client_data.hpp" +#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 = 99; + + // internal + constexpr int abi_alert_count = 128; + + // internal + enum class alert_priority : std::uint8_t + { + // the order matters here. Lower value means lower priority, and will + // start getting dropped earlier when the alert queue is filling up + normal = 0, + high, + critical, + meta + }; + + // 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_3 + + // This is a base class for alerts that are associated with a + // specific torrent. It contains a handle to the torrent. + // + // Note that by the time the client receives a torrent_alert, its + // ``handle`` member may be invalid. + struct TORRENT_EXPORT torrent_alert : alert + { + // internal + TORRENT_UNEXPORT torrent_alert(aux::stack_allocator& alloc, torrent_handle const& h); + TORRENT_UNEXPORT torrent_alert(torrent_alert&&) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static int const 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: + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT peer_alert(aux::stack_allocator& alloc, torrent_handle const& h, + tcp::endpoint const& i, peer_id const& pi); + TORRENT_UNEXPORT peer_alert(peer_alert&& rhs) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static int const 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. + TORRENT_DEPRECATED aux::noexcept_movable 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 + TORRENT_UNEXPORT tracker_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, string_view u); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static int const 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 + TORRENT_DEPRECATED std::string url; +#endif + }; + +#define TORRENT_DEFINE_ALERT_IMPL(name, seq, prio) \ + name(name&&) noexcept = default; \ + static alert_priority const priority = prio; \ + static int const 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 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // 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 ``alert_category::status`` 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_UNEXPORT 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; + }; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + + // The ``torrent_removed_alert`` is posted whenever a torrent is removed. Since + // the torrent handle in its base class will usually be invalid (since the torrent + // is already removed) it has the info hash as a member, to identify it. + // It's posted when the ``alert_category::status`` bit is set in the alert_mask. + // + // Note that the ``handle`` remains valid for some time after + // torrent_removed_alert is posted, as long as some internal libtorrent + // task (such as an I/O task) refers to it. Additionally, other alerts like + // save_resume_data_alert may be posted after torrent_removed_alert. + // To synchronize on whether the torrent has been removed or not, call + // torrent_handle::in_session(). This will return true before + // torrent_removed_alert is posted, and false afterward. + // + // 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_UNEXPORT torrent_removed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, info_hash_t const& ih, client_data_t userdata); + + 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; +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + info_hash_t info_hashes; + + // '`userdata`` as set in add_torrent_params at torrent creation. + // This can be used to associate this torrent with related data + // in the client application more efficiently than info_hashes. + client_data_t userdata; + }; + + // 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 + TORRENT_UNEXPORT read_piece_alert(aux::stack_allocator& alloc, torrent_handle const& h + , piece_index_t p, boost::shared_array d, int s); + TORRENT_UNEXPORT 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 + TORRENT_DEPRECATED error_code 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 + TORRENT_UNEXPORT 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) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + static constexpr alert_category_t static_category = + alert_category::file_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + 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 + TORRENT_UNEXPORT file_renamed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , string_view n, string_view old, 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; + + // returns the new and previous file name, respectively. + char const* new_name() const; + char const* old_name() const; + + // refers to the index of the file that was renamed, + file_index_t const index; + private: + aux::allocation_slot m_name_idx; + aux::allocation_slot m_old_name_idx; +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_warnings_push.hpp" + + public: + TORRENT_DEPRECATED std::string name; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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. + struct TORRENT_EXPORT tracker_error_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT tracker_error_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , int times, string_view u, operation_t op, 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; + + // This member says how many times in a row this tracker has failed. + int const times_in_row; + + // the error code indicating why the tracker announce failed. If it is + // is ``lt::errors::tracker_failure`` the failure_reason() might contain + // a more detailed description of why the tracker rejected the request. + // HTTP status codes indicating errors are also set in this field. + error_code const error; + + operation_t op; + + // if the tracker sent a "failure reason" string, it will be returned + // here. + char const* failure_reason() const; + + // hidden + char const* error_message() const { return failure_reason(); } + + private: + aux::allocation_slot m_msg_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED int const status_code; + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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. + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is generated when a scrape request succeeds. + struct TORRENT_EXPORT scrape_reply_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT scrape_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, error_code const& e); + TORRENT_UNEXPORT 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. + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT tracker_announce_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, event_t 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. See event_t. + event_t 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_DEPRECATED int const operation; + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is posted every time an incoming peer connection both + // successfully passes the protocol handshake and is associated with a + // torrent, or an outgoing peer connection attempt succeeds. For arbitrary + // incoming connections, see incoming_connection_alert. + struct TORRENT_EXPORT peer_connect_alert final : peer_alert + { + enum class direction_t { in, out }; + + // internal + TORRENT_UNEXPORT peer_connect_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, socket_type_t type, direction_t direction); + + TORRENT_DEFINE_ALERT(peer_connect_alert, 23) + + static constexpr alert_category_t static_category = alert_category::connect; + std::string message() const override; + + // Tells you if the peer was incoming or outgoing + direction_t direction; + + socket_type_t 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 + TORRENT_UNEXPORT peer_disconnected_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , peer_id const& peer_id, operation_t op, socket_type_t 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 + socket_type_t 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 + TORRENT_DEPRECATED int const operation; + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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_UNEXPORT 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 + TORRENT_UNEXPORT piece_finished_alert(aux::stack_allocator& alloc, + torrent_handle const& h, piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(piece_finished_alert, 27) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + static constexpr alert_category_t static_category = + alert_category::piece_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + 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 + TORRENT_UNEXPORT 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) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + | alert_category::peer + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + 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 + TORRENT_UNEXPORT 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) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + | alert_category::peer + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + 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 + TORRENT_UNEXPORT 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) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + 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 + TORRENT_UNEXPORT 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) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED char const* 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT storage_moved_alert(aux::stack_allocator& alloc + , torrent_handle const& h, string_view p, string_view old); + + 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 and from, respectively. + char const* storage_path() const; + char const* old_path() const; + + private: + aux::allocation_slot m_path_idx; + aux::allocation_slot m_old_path_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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: + TORRENT_DEPRECATED char const* operation; + // If the error happened for a specific file, ``file`` is its path. + TORRENT_DEPRECATED std::string file; +#endif + }; + + // This alert is generated when a request to delete the files of a torrent complete. + // + // This alert is posted in the ``alert_category::storage`` category, and that bit + // needs to be set in the alert_mask. + struct TORRENT_EXPORT torrent_deleted_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_deleted_alert(aux::stack_allocator& alloc + , torrent_handle const& h, info_hash_t 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; + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + + // 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_hashes`` member + // is hence the main way of identifying which torrent just completed the delete. + info_hash_t info_hashes; + }; + + // 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_UNEXPORT torrent_delete_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e, info_hash_t 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; + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + // the info hash of the torrent whose files failed to be deleted + info_hash_t info_hashes; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT save_resume_data_alert(aux::stack_allocator& alloc + , add_torrent_params&& params + , torrent_handle const& h); + TORRENT_UNEXPORT 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. + TORRENT_DEPRECATED std::shared_ptr 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 + TORRENT_UNEXPORT 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 + TORRENT_DEPRECATED std::string 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_UNEXPORT 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_UNEXPORT 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_UNEXPORT 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 + TORRENT_UNEXPORT url_seed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , string_view u, error_code const& e); + TORRENT_UNEXPORT 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 + TORRENT_DEPRECATED std::string url; + + // the error message, potentially from the server + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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: + TORRENT_DEPRECATED char const* operation; + // the path to the file that was accessed when the error occurred. + TORRENT_DEPRECATED std::string file; + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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(); + // 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_hashes().get_best().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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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; + }; + + // 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 + TORRENT_UNEXPORT 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); + TORRENT_UNEXPORT 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); + TORRENT_UNEXPORT 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); + TORRENT_UNEXPORT 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. + TORRENT_DEPRECATED int const operation; + + // the address and port libtorrent attempted to listen on + TORRENT_DEPRECATED aux::noexcept_movable endpoint; + + // the type of listen socket this alert refers to. + TORRENT_DEPRECATED socket_type_t 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 + TORRENT_UNEXPORT listen_succeeded_alert(aux::stack_allocator& alloc + , lt::address const& listen_addr + , int listen_port + , lt::socket_type_t t); + TORRENT_UNEXPORT listen_succeeded_alert(aux::stack_allocator& alloc + , tcp::endpoint const& ep + , lt::socket_type_t t); + TORRENT_UNEXPORT 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. + TORRENT_DEPRECATED aux::noexcept_movable endpoint; + + // the type of listen socket this alert refers to. + TORRENT_DEPRECATED socket_type_t 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 + TORRENT_UNEXPORT portmap_error_alert(aux::stack_allocator& alloc, port_mapping_t i + , portmap_transport t, error_code const& e, address const& local); + + 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; + + // the local network the port mapper is running on + aux::noexcept_movable
    local_address; + + // tells you what failed. + error_code const error; +#if TORRENT_ABI_VERSION == 1 + // is 0 for NAT-PMP and 1 for UPnP. + TORRENT_DEPRECATED int const map_type; + + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT portmap_alert(aux::stack_allocator& alloc, port_mapping_t i, int port + , portmap_transport t, portmap_protocol protocol, address const& local); + + 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; + + // the local network the port mapper is running on + aux::noexcept_movable
    local_address; + +#if TORRENT_ABI_VERSION == 1 + enum TORRENT_DEPRECATED_ENUM protocol_t + { + tcp, + udp + }; + + // the protocol this mapping was for. one of protocol_t enums + TORRENT_DEPRECATED int const protocol; + + // 0 for NAT-PMP and 1 for UPnP. + TORRENT_DEPRECATED int const 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 + TORRENT_UNEXPORT portmap_log_alert(aux::stack_allocator& alloc, portmap_transport t + , const char* m, address const& local); + + 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 local network the port mapper is running on + aux::noexcept_movable
    local_address; + + // 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: + TORRENT_DEPRECATED int const map_type; + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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. + TORRENT_DEPRECATED char const* operation; + + // If the error happened to a specific file, ``file`` is the path to it. + TORRENT_DEPRECATED std::string file; + TORRENT_DEPRECATED std::string 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 + TORRENT_UNEXPORT 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, + ssrf_mitigation + }; + + // 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 ``alert_category::dht`` category. + struct TORRENT_EXPORT dht_announce_alert final : alert + { + // internal + TORRENT_UNEXPORT 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 ``alert_category::dht`` category. + struct TORRENT_EXPORT dht_get_peers_alert final : alert + { + // internal + TORRENT_UNEXPORT 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; + }; + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/aux_/disable_deprecation_warnings_push.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 TORRENT_DEPRECATED_EXPORT stats_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT 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; + }; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif // TORRENT_ABI_VERSION + + // 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 ``alert_category::storage`` 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 + TORRENT_UNEXPORT 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 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // 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 + TORRENT_UNEXPORT 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; + }; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_DEPRECATED std::string trackerid; +#endif + }; + + // This alert is posted when the initial DHT bootstrap is done. + struct TORRENT_EXPORT dht_bootstrap_alert final : alert + { + // internal + explicit TORRENT_UNEXPORT 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_UNEXPORT 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. + TORRENT_DEPRECATED std::string 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_UNEXPORT 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 + TORRENT_DEPRECATED error_code const 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 + TORRENT_UNEXPORT incoming_connection_alert(aux::stack_allocator& alloc + , socket_type_t 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 + socket_type_t 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. + TORRENT_DEPRECATED aux::noexcept_movable 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 + TORRENT_UNEXPORT add_torrent_alert(aux::stack_allocator& alloc, torrent_handle const& h + , add_torrent_params 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; + + // This contains copies of the most important fields from the original + // add_torrent_params object, passed to add_torrent() or + // async_add_torrent(). Specifically, these fields are copied: + // + // * version + // * ti + // * name + // * save_path + // * userdata + // * tracker_id + // * flags + // * info_hash + // + // the info_hash field will be updated with the info-hash of the torrent + // specified by ``ti``. + 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 ``alert_category::status``, but it's not subject to + // filtering, since it's only manually posted anyway. + struct TORRENT_EXPORT state_update_alert final : alert + { + // internal + TORRENT_UNEXPORT 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 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + 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; + }; +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#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. This alert does not + // have a category, since it's only posted in response to an API call. It + // is not subject to the alert_mask filter. + // + // 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 + TORRENT_UNEXPORT session_stats_alert(aux::stack_allocator& alloc, counters const& cnt); + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" +#endif + + TORRENT_DEFINE_ALERT_PRIO(session_stats_alert, 70, alert_priority::critical) + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + + static constexpr alert_category_t static_category = {}; + 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 + TORRENT_DEPRECATED std::array const values; +#else + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_counters_idx; +#endif + }; + + // 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 + TORRENT_UNEXPORT 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 + TORRENT_DEPRECATED op_t const 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 + TORRENT_UNEXPORT dht_immutable_item_alert(aux::stack_allocator& alloc, sha1_hash const& t + , entry 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 + TORRENT_UNEXPORT dht_mutable_item_alert(aux::stack_allocator& alloc + , std::array const& k, std::array const& sig + , std::int64_t sequence, string_view s, entry 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 + TORRENT_UNEXPORT dht_put_alert(aux::stack_allocator& alloc, sha1_hash const& t, int n); + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 ``alert_category::dht`` category. + struct TORRENT_EXPORT dht_outgoing_get_peers_alert final : alert + { + // internal + TORRENT_UNEXPORT 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 + TORRENT_DEPRECATED aux::noexcept_movable 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 + TORRENT_UNEXPORT log_alert(aux::stack_allocator& alloc, char const* log); + TORRENT_UNEXPORT 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_UNEXPORT 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 + TORRENT_UNEXPORT 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 ``alert_category::error``. + struct TORRENT_EXPORT lsd_error_alert final : alert + { + // internal + TORRENT_UNEXPORT lsd_error_alert(aux::stack_allocator& alloc, error_code const& ec + , address const& local); + + TORRENT_DEFINE_ALERT(lsd_error_alert, 82) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // the local network the corresponding local service discovery is running + // on + aux::noexcept_movable
    local_address; + + // 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 + TORRENT_UNEXPORT dht_stats_alert(aux::stack_allocator& alloc + , std::vector table + , std::vector requests + , sha1_hash id, udp::endpoint ep); + + TORRENT_DEFINE_ALERT(dht_stats_alert, 83) + + static constexpr alert_category_t static_category = {}; + 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; + + // the node ID of the DHT node instance + sha1_hash nid; + + // the local socket this DHT node is running on + aux::noexcept_movable local_endpoint; + }; + + // 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 + TORRENT_UNEXPORT 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 alert_category::dht_log 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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: + TORRENT_DEPRECATED direction_t 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 + TORRENT_UNEXPORT dht_get_peers_reply_alert(aux::stack_allocator& alloc + , sha1_hash const& ih + , std::vector const& peers); + + 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 + TORRENT_UNEXPORT dht_direct_response_alert(aux::stack_allocator& alloc, client_data_t userdata + , udp::endpoint const& addr, bdecode_node const& response); + + // internal + // for when there was a timeout so we don't have a response + TORRENT_UNEXPORT dht_direct_response_alert(aux::stack_allocator& alloc, client_data_t 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; + + client_data_t 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: + TORRENT_DEPRECATED aux::noexcept_movable 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 + // alert_category::picker_log). + struct TORRENT_EXPORT picker_log_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 + TORRENT_UNEXPORT 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 TORRENT_UNEXPORT session_stats_header_alert(aux::stack_allocator& alloc); + TORRENT_DEFINE_ALERT(session_stats_header_alert, 92) + + static constexpr alert_category_t static_category = {}; + 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 + TORRENT_UNEXPORT dht_sample_infohashes_alert(aux::stack_allocator& alloc + , sha1_hash const& nid + , 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; + + // id of the node the request was sent to (and this response was received from) + sha1_hash node_id; + + // 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 ``alert_category::upload`` category. + struct TORRENT_EXPORT block_uploaded_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT 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) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::upload + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + 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 TORRENT_UNEXPORT alerts_dropped_alert(aux::stack_allocator& alloc + , std::bitset const&); + TORRENT_DEFINE_ALERT_PRIO(alerts_dropped_alert, 95, alert_priority::meta) + + 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; + static_assert(num_alert_types <= abi_alert_count, "need to increase bitset. This is an ABI break"); + }; + + // this alert is posted with SOCKS5 related errors, when a SOCKS5 proxy is + // configured. It's enabled with the alert_category::error 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; + }; + + // posted when a prioritize_files() or file_priority() update of the file + // priorities complete, which requires a round-trip to the disk thread. + // + // If the disk operation fails this alert won't be posted, but a + // file_error_alert is posted instead, and the torrent is stopped. + struct TORRENT_EXPORT file_prio_alert final : torrent_alert + { + // internal + explicit file_prio_alert(aux::stack_allocator& alloc, torrent_handle h); + TORRENT_DEFINE_ALERT(file_prio_alert, 97) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // the error + error_code error; + + // the operation that failed + operation_t op; + }; + +TORRENT_VERSION_NAMESPACE_3_END + + // this alert may be posted when the initial checking of resume data and files + // on disk (just existence, not piece hashes) completes. If a file belonging + // to the torrent is found on disk, but is larger than the file in the + // torrent, that's when this alert is posted. + // the client may want to call truncate_files() in that case, or perhaps + // interpret it as a sign that some other file is in the way, that shouldn't + // be overwritten. + struct TORRENT_EXPORT oversized_file_alert final : torrent_alert + { + // internal + explicit oversized_file_alert(aux::stack_allocator& alloc, torrent_handle h); + TORRENT_DEFINE_ALERT(oversized_file_alert, 98) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // hidden + file_index_t reserved; + }; + + // 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 + +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/announce_entry.hpp b/include/libtorrent/announce_entry.hpp new file mode 100644 index 0000000..1f3ce25 --- /dev/null +++ b/include/libtorrent/announce_entry.hpp @@ -0,0 +1,291 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2016, 2018, 2020, Alden Torres +Copyright (c) 2017-2018, 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_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_/array.hpp" +#include "libtorrent/info_hash.hpp" + +#include +#include +#include + +namespace libtorrent { + + struct torrent; + +TORRENT_VERSION_NAMESPACE_2 + + struct TORRENT_EXPORT announce_infohash + { + // internal + TORRENT_UNEXPORT announce_infohash(); + + // 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 time of next tracker announce + time_point32 next_announce = (time_point32::min)(); + + // no announces before this time + time_point32 min_announce = (time_point32::min)(); + + // 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; + +#if TORRENT_ABI_VERSION <= 2 + // reset announce counters and clears the started sent flag. + // The announce_endpoint will look like we've never talked to + // the tracker. + TORRENT_DEPRECATED void reset(); + + // updates the failure counter and time-outs for re-trying. + // This is called when the tracker announce fails. + TORRENT_DEPRECATED 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. + TORRENT_DEPRECATED 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. + TORRENT_DEPRECATED bool is_working() const { return fails == 0; } +#endif + }; + + // announces are sent to each tracker using every listen socket + // this class holds information about one listen socket for one tracker +#if TORRENT_ABI_VERSION <= 2 + // this is to suppress deprecation warnings from implicit move constructor +#include "libtorrent/aux_/disable_warnings_push.hpp" +#endif + struct TORRENT_EXPORT announce_endpoint + { +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + + announce_endpoint(); + + // the local endpoint of the listen interface associated with this endpoint + tcp::endpoint local_endpoint; + + // torrents can be announced using multiple info hashes + // for different protocol versions + + // info_hashes[0] is the v1 info hash (SHA1) + // info_hashes[1] is the v2 info hash (truncated SHA-256) + aux::array info_hashes; + +#if TORRENT_ABI_VERSION <= 2 + // reset announce counters and clears the started sent flag. + // The announce_endpoint will look like we've never talked to + // the tracker. + TORRENT_DEPRECATED void reset(); + + // deprecated in 2.0, use announce_infohash::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, std::uint8_t fail_limit) const; + + // deprecated in 2.0, use announce_infohash::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; + + // for backwards compatibility + TORRENT_DEPRECATED time_point32 next_announce = (time_point32::min)(); + TORRENT_DEPRECATED time_point32 min_announce = (time_point32::min)(); + TORRENT_DEPRECATED std::string message; + TORRENT_DEPRECATED error_code last_error; + TORRENT_DEPRECATED int scrape_incomplete = -1; + TORRENT_DEPRECATED int scrape_complete = -1; + TORRENT_DEPRECATED int scrape_downloaded = -1; + TORRENT_DEPRECATED std::uint8_t fails : 7; + TORRENT_DEPRECATED bool updating : 1; + TORRENT_DEPRECATED bool start_sent : 1; + TORRENT_DEPRECATED bool complete_sent : 1; +#endif + + // set to false to not announce from this endpoint + bool enabled = true; + }; + + // 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 + TORRENT_DEPRECATED std::uint8_t fails:7; + TORRENT_DEPRECATED bool send_stats:1; + TORRENT_DEPRECATED bool start_sent:1; + TORRENT_DEPRECATED bool complete_sent:1; + // internal + TORRENT_DEPRECATED bool triggered_manually:1; + TORRENT_DEPRECATED bool updating:1; +#endif + +#if TORRENT_ABI_VERSION <= 2 + // reset announce counters and clears the started sent flag. + // The announce_entry will look like we've never talked to + // the tracker. + TORRENT_DEPRECATED void reset(); + + // trims whitespace characters from the beginning of the URL. + TORRENT_DEPRECATED void trim(); +#endif + +#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 + }; + +TORRENT_VERSION_NAMESPACE_2_END + +} + +#endif diff --git a/include/libtorrent/assert.hpp b/include/libtorrent/assert.hpp new file mode 100644 index 0000000..2bf6ee8 --- /dev/null +++ b/include/libtorrent/assert.hpp @@ -0,0 +1,130 @@ +/* + +Copyright (c) 2007-2008, 2010-2011, 2013-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_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_/alert_manager.hpp b/include/libtorrent/aux_/alert_manager.hpp new file mode 100644 index 0000000..d5f6237 --- /dev/null +++ b/include/libtorrent/aux_/alert_manager.hpp @@ -0,0 +1,180 @@ +/* + +Copyright (c) 2003-2013, Daniel Wallin +Copyright (c) 2013, 2015-2020, Arvid Norberg +Copyright (c) 2016, 2018, 2020, 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_ALERT_MANAGER_HPP_INCLUDED +#define TORRENT_ALERT_MANAGER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/aux_/heterogeneous_queue.hpp" +#include "libtorrent/stack_allocator.hpp" +#include "libtorrent/alert_types.hpp" // for abi_alert_count +#include "libtorrent/aux_/array.hpp" + +#include +#include // for std::forward +#include +#include +#include +#include + +#ifndef TORRENT_DISABLE_EXTENSIONS +#include "libtorrent/extensions.hpp" +#include // for shared_ptr +#include +#endif + +namespace libtorrent { +namespace aux { + + struct TORRENT_EXTRA_EXPORT alert_manager + { + explicit alert_manager(int queue_limit + , alert_category_t alert_mask = alert_category::error); + + 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); + + heterogeneous_queue& queue = m_alerts[m_generation]; + + // 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 (queue.size() / (1 + static_cast(T::priority)) >= m_queue_size_limit) + { + // record that we dropped an alert of this type + m_dropped.set(T::alert_type); + return; + } + + T& alert = queue.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/aux_/aligned_storage.hpp b/include/libtorrent/aux_/aligned_storage.hpp new file mode 100644 index 0000000..361b928 --- /dev/null +++ b/include/libtorrent/aux_/aligned_storage.hpp @@ -0,0 +1,61 @@ +/* + +Copyright (c) 2017, 2020, Arvid Norberg +Copyright (c) 2017, 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_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..fc873a4 --- /dev/null +++ b/include/libtorrent/aux_/aligned_union.hpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2017, 2020, Arvid Norberg +Copyright (c) 2017, 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_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..b81ba49 --- /dev/null +++ b/include/libtorrent/aux_/alloca.hpp @@ -0,0 +1,117 @@ +/* + +Copyright (c) 2009-2010, 2012, 2017-2020, Arvid Norberg +Copyright (c) 2017, 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_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 +{ + static std::ptrdiff_t const cutoff = 4096 / sizeof(T); + + span objects; + ~alloca_destructor() + { + if (objects.size() > cutoff) + { + delete [] objects.data(); + } + else + { + for (auto& o : objects) + { + TORRENT_UNUSED(o); + o.~T(); + } + } + } +}; + +}} + +#if defined TORRENT_WINDOWS || defined TORRENT_MINGW + +#include +#define TORRENT_ALLOCA_FUN _alloca + +#elif defined TORRENT_BSD + +#include +#define TORRENT_ALLOCA_FUN alloca + +#else + +#include +#define TORRENT_ALLOCA_FUN alloca + +#endif + +#define TORRENT_ALLOCA(v, t, n) ::libtorrent::span v; { \ + auto TORRENT_ALLOCA_size = ::libtorrent::aux::numeric_cast(n); \ + if (TORRENT_ALLOCA_size > ::libtorrent::aux::alloca_destructor::cutoff) {\ + v = ::libtorrent::span(new t[::libtorrent::aux::numeric_cast(n)], TORRENT_ALLOCA_size); \ + } \ + else { \ + auto* TORRENT_ALLOCA_tmp = static_cast(TORRENT_ALLOCA_FUN(sizeof(t) * static_cast(n))); \ + 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 // 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..dbe2678 --- /dev/null +++ b/include/libtorrent/aux_/allocating_handler.hpp @@ -0,0 +1,362 @@ +/* + +Copyright (c) 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2019, Steven Siloti +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 +#include // for shared_ptr + +#ifdef TORRENT_ASIO_DEBUGGING +#include "libtorrent/debug.hpp" +#endif + +namespace libtorrent { namespace aux { + +#ifdef BOOST_ASIO_ENABLE_HANDLER_TRACKING + constexpr std::size_t tracking = 8; +#else + constexpr std::size_t tracking = 0; +#endif + +#if defined _MSC_VER || defined __MINGW64__ + + // windows +#if _HAS_ITERATOR_DEBUGGING > 0 + constexpr std::size_t debug_read_iter = 34 * sizeof(void*); + constexpr std::size_t debug_write_iter = 34 * sizeof(void*); + constexpr std::size_t debug_tick = 4 * sizeof(void*); +#else + constexpr std::size_t debug_read_iter = 0; + constexpr std::size_t debug_write_iter = 0; + constexpr std::size_t debug_tick = 0; +#endif +#if TORRENT_USE_SSL + constexpr std::size_t openssl_read_cost = 26 + 14 * sizeof(void*); + constexpr std::size_t openssl_write_cost = 26 + 14 * sizeof(void*); +#else + constexpr std::size_t openssl_read_cost = 0; + constexpr std::size_t openssl_write_cost = 0; +#endif + + constexpr std::size_t read_handler_max_size = tracking + debug_read_iter + openssl_read_cost + 102 + 9 * sizeof(void*); + constexpr std::size_t write_handler_max_size = tracking + debug_write_iter + openssl_write_cost + 102 + 9 * sizeof(void*); + constexpr std::size_t udp_handler_max_size = tracking + debug_tick + 144 + 9 * sizeof(void*); + constexpr std::size_t utp_handler_max_size = tracking + debug_tick + 168 + 9 * sizeof(void*); + constexpr std::size_t tick_handler_max_size = tracking + debug_tick + 168; + constexpr std::size_t abort_handler_max_size = tracking + debug_tick + 104; + constexpr std::size_t submit_handler_max_size = tracking + debug_tick + 104; + constexpr std::size_t deferred_handler_max_size = tracking + debug_tick + 112; +#else + + // non-windows + +#ifdef _GLIBCXX_DEBUG +#if defined __clang__ + constexpr std::size_t debug_read_iter = 12 * sizeof(void*); + constexpr std::size_t debug_write_iter = 12 * sizeof(void*); +#else + constexpr std::size_t debug_write_iter = 8 * sizeof(void*); + constexpr std::size_t debug_read_iter = 12 * sizeof(void*); +#endif +#else + constexpr std::size_t debug_read_iter = 0; + constexpr std::size_t debug_write_iter = 0; +#endif + +#if TORRENT_USE_SSL +#ifdef __clang__ + constexpr std::size_t openssl_read_cost = 264; + constexpr std::size_t openssl_write_cost = 216; +#else + constexpr std::size_t openssl_read_cost = 152; + constexpr std::size_t openssl_write_cost = 152; +#endif +#else + constexpr std::size_t openssl_read_cost = 0; + constexpr std::size_t openssl_write_cost = 0; +#endif + +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + constexpr std::size_t fuzzer_write_cost = 32; + constexpr std::size_t fuzzer_read_cost = 80; +#else + constexpr std::size_t fuzzer_write_cost = 0; + constexpr std::size_t fuzzer_read_cost = 0; +#endif + constexpr std::size_t write_handler_max_size = tracking + debug_write_iter + openssl_write_cost + fuzzer_write_cost + 176; + constexpr std::size_t read_handler_max_size = tracking + debug_read_iter + openssl_read_cost + fuzzer_read_cost + 176; + constexpr std::size_t udp_handler_max_size = tracking + 168; + constexpr std::size_t utp_handler_max_size = tracking + 192; + constexpr std::size_t abort_handler_max_size = tracking + 72; + constexpr std::size_t submit_handler_max_size = tracking + 72; + constexpr std::size_t deferred_handler_max_size = tracking + 80; + constexpr std::size_t tick_handler_max_size = tracking + 136; +#endif + + enum HandlerName + { + // when adding a handler here, be sure to update handler_names in + // debug.hpp as well + write_handler, read_handler, udp_handler, tick_handler, abort_handler, + defer_handler, utp_handler, submit_handler + }; + + // 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 + { + handler_storage() = default; + static constexpr std::size_t size = Size; + static constexpr HandlerName name = Name; + + typename aux::aligned_storage::type bytes; +#if TORRENT_USE_ASSERTS + bool used = false; +#endif + handler_storage(handler_storage const&) = delete; + }; + + 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() {} + }; + + template + struct required_size { static std::size_t const value = V; }; + + template + struct available_size { static std::size_t const value = V; }; + + template + struct assert_message + { + static_assert(Required::value <= Available::value + , "Handler buffer not large enough, please increase it"); + static std::size_t const value = Available::value; + }; + + template + struct handler_allocator + { + template + friend struct handler_allocator; + + using value_type = T; + using size_type = std::size_t; + + friend bool operator==(handler_allocator lhs, handler_allocator rhs) + { return lhs.m_storage == rhs.m_storage; } + friend bool operator!=(handler_allocator lhs, handler_allocator rhs) + { return lhs.m_storage != rhs.m_storage; } + + template + struct rebind { + using other = handler_allocator + , available_size, Name>::value, Name>; + static_assert(alignof(U) <= alignof(std::max_align_t), "handler storage is not correctly aligned"); + }; + + explicit handler_allocator(handler_storage* s) : m_storage(s) {} + template + handler_allocator(handler_allocator const& other) : m_storage(other.m_storage) {} + + T* allocate(std::size_t size) + { + TORRENT_UNUSED(size); + TORRENT_ASSERT_VAL(size == 1, size); + TORRENT_ASSERT_VAL(sizeof(T) <= Size, sizeof(T)); + TORRENT_ASSERT(!m_storage->used); +#if TORRENT_USE_ASSERTS + m_storage->used = true; +#endif +#ifdef TORRENT_ASIO_DEBUGGING + record_handler_allocation(static_cast(Name), Size); +#endif + return reinterpret_cast(&m_storage->bytes); + } + + void deallocate(T* ptr, std::size_t size) + { + TORRENT_UNUSED(ptr); + TORRENT_UNUSED(size); + + TORRENT_ASSERT_VAL(size == 1, size); + TORRENT_ASSERT_VAL(sizeof(T) <= Size, sizeof(T)); + TORRENT_ASSERT(ptr == reinterpret_cast(&m_storage->bytes)); + TORRENT_ASSERT(m_storage->used); +#if TORRENT_USE_ASSERTS + m_storage->used = false; +#endif + } + + private: + handler_storage* m_storage; + }; + + // 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) + { +#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 + } + + using allocator_type = handler_allocator; + + allocator_type get_allocator() const noexcept + { return allocator_type{storage}; } + + private: + + 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); + } + + // TODO: in C++17, Handler and Storage could just use "auto" + template + struct handler + { + explicit handler(std::shared_ptr p) : ptr_(std::move(p)) {} + + std::shared_ptr ptr_; + + template + void operator()(A&&... a) + { +#ifdef BOOST_NO_EXCEPTIONS + (ptr_.get()->*Handler)(std::forward(a)...); +#else + try + { + (ptr_.get()->*Handler)(std::forward(a)...); + } + catch (system_error const& e) + { + (ptr_.get()->*ErrorHandler)(e.code()); + } + catch (std::exception const& e) + { + (ptr_.get()->*ExceptHandler)(e); + } + catch (...) + { + // this is pretty bad + TORRENT_ASSERT(false); + std::runtime_error e("unknown exception"); + (ptr_.get()->*ExceptHandler)(e); + } +#endif + } + + using allocator_type = handler_allocator; + + allocator_type get_allocator() const noexcept + { return allocator_type{&(ptr_.get()->*Storage)}; } + }; +} +} + +#endif diff --git a/include/libtorrent/aux_/announce_entry.hpp b/include/libtorrent/aux_/announce_entry.hpp new file mode 100644 index 0000000..cfa4865 --- /dev/null +++ b/include/libtorrent/aux_/announce_entry.hpp @@ -0,0 +1,216 @@ +/* + +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2017-2018, Steven Siloti +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_AUX_ANNOUNCE_ENTRY_HPP_INCLUDED +#define TORRENT_AUX_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 "libtorrent/aux_/array.hpp" +#include "libtorrent/info_hash.hpp" + +#include +#include +#include + +namespace libtorrent { + struct torrent; +namespace aux { + + struct TORRENT_EXTRA_EXPORT announce_infohash + { + announce_infohash(); + + // 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 time of next tracker announce + time_point32 next_announce = (time_point32::min)(); + + // no announces before this time + time_point32 min_announce = (time_point32::min)(); + + // 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; + + // 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; } + }; + + struct announce_entry; + + // announces are sent to each tracker using every listen socket + // this class holds information about one listen socket for one tracker + struct TORRENT_EXTRA_EXPORT announce_endpoint + { + // internal + announce_endpoint(aux::listen_socket_handle const& s, bool completed); + + // the local endpoint of the listen interface associated with this endpoint + tcp::endpoint local_endpoint; + + // torrents can be announced using multiple info hashes + // for different protocol versions + + // info_hashes[0] is the v1 info hash (SHA1) + // info_hashes[1] is the v2 info hash (truncated SHA-256) + aux::array info_hashes; + + // reset announce counters and clears the started sent flag. + // The announce_endpoint will look like we've never talked to + // the tracker. + void reset(); + + // set to false to not announce from this endpoint + bool enabled : 1; + + // internal + aux::listen_socket_handle socket; + }; + + // this class holds information about one bittorrent tracker, as it + // relates to a specific torrent. + struct TORRENT_EXTRA_EXPORT announce_entry + { + // constructs a tracker announce entry with ``u`` as the URL. + explicit announce_entry(string_view u); + + // constructs the internal announce entry from the user facing one + explicit announce_entry(lt::announce_entry const&); + 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; + + // 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; + + // reset announce counters and clears the started sent flag. + // The announce_entry will look like we've never talked to + // the tracker. + void reset(); + + // internal + announce_endpoint* find_endpoint(aux::listen_socket_handle const& s); + }; + +} +} + +#endif + diff --git a/include/libtorrent/aux_/apply_pad_files.hpp b/include/libtorrent/aux_/apply_pad_files.hpp new file mode 100644 index 0000000..514e4db --- /dev/null +++ b/include/libtorrent/aux_/apply_pad_files.hpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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_APPLY_PAD_FILES_HPP_INCLUDED +#define TORRENT_APPLY_PAD_FILES_HPP_INCLUDED + +#include "libtorrent/file_storage.hpp" +#include "libtorrent/units.hpp" + +namespace libtorrent { +namespace aux { + +// calls fun for every piece that overlaps a pad file, passing in the number +// of bytes, in that piece, that's a padfile +template +void apply_pad_files(file_storage const& fs, Fun&& fun) +{ + for (auto const i : fs.file_range()) + { + if (!fs.pad_file_at(i) || fs.file_size(i) == 0) continue; + + // pr points to the last byte of the pad file + peer_request const pr = fs.map_file(i, fs.file_size(i) - 1, 0); + + int const piece_size = fs.piece_length(); + + // This pad file may be the last file in the torrent, and the + // last piece may have an odd size. + if ((pr.start + 1) % piece_size != 0 && i < prev(fs.end_file())) + { + // this is a pre-requisite of the piece picker. Pad files + // that don't align with pieces are kind of useless anyway. + // They probably aren't real padfiles, treat them as normal + // files. + continue; + } + + // A pad file may span multiple pieces. This is especially + // likely in v2 torrents where file sizes are aligned to powers + // of two pieces. We loop from the end of the pad file + // + // For example, we may have this situation: + // + // pr.start + // | + // v + // +-----+-----+-----+ + // | ##|#####|#####| + // +-----+-----+-----+ + // \ / + // - file_size - + // + // We need to declare all #-parts of the pieces as pad bytes to + // the piece picker. + + piece_index_t piece = pr.piece; + std::int64_t pad_bytes_left = fs.file_size(i); + + while (pad_bytes_left > 0) + { + // The last piece may have an odd size, that's why + // we ask for the piece size for every piece. (it would be + // odd, but it's still possible). + int const bytes = int(std::min(pad_bytes_left, std::int64_t(fs.piece_size(piece)))); + TORRENT_ASSERT(bytes > 0); + fun(piece, bytes); + pad_bytes_left -= bytes; + --piece; + } + } +} + +} // namespace aux +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/aux_/array.hpp b/include/libtorrent/aux_/array.hpp new file mode 100644 index 0000000..9b2e076 --- /dev/null +++ b/include/libtorrent/aux_/array.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2017, Alden Torres +Copyright (c) 2018, 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_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_/bandwidth_limit.hpp b/include/libtorrent/aux_/bandwidth_limit.hpp new file mode 100644 index 0000000..d982f41 --- /dev/null +++ b/include/libtorrent/aux_/bandwidth_limit.hpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2007, 2009-2010, 2012, 2014, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2020, 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_BANDWIDTH_CHANNEL_HPP_INCLUDED +#define TORRENT_BANDWIDTH_CHANNEL_HPP_INCLUDED + +#include +#include + +#include "libtorrent/assert.hpp" + +namespace libtorrent { +namespace aux { + +// 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/aux_/bandwidth_manager.hpp b/include/libtorrent/aux_/bandwidth_manager.hpp new file mode 100644 index 0000000..e4599ef --- /dev/null +++ b/include/libtorrent/aux_/bandwidth_manager.hpp @@ -0,0 +1,94 @@ +/* + +Copyright (c) 2007, 2009, 2011-2016, 2020, Arvid Norberg +Copyright (c) 2016, 2018, 2020, 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_BANDWIDTH_MANAGER_HPP_INCLUDED +#define TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED + +#include +#include + +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/bandwidth_limit.hpp" +#include "libtorrent/aux_/bandwidth_queue_entry.hpp" +#include "libtorrent/aux_/bandwidth_socket.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent { +namespace aux { + +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/aux_/bandwidth_queue_entry.hpp b/include/libtorrent/aux_/bandwidth_queue_entry.hpp new file mode 100644 index 0000000..7ba427e --- /dev/null +++ b/include/libtorrent/aux_/bandwidth_queue_entry.hpp @@ -0,0 +1,77 @@ +/* + +Copyright (c) 2007, 2009, 2012, 2014-2015, 2020, Arvid Norberg +Copyright (c) 2016, 2018, 2020, 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_BANDWIDTH_QUEUE_ENTRY_HPP_INCLUDED +#define TORRENT_BANDWIDTH_QUEUE_ENTRY_HPP_INCLUDED + +#include + +#include "libtorrent/aux_/bandwidth_limit.hpp" +#include "libtorrent/aux_/bandwidth_socket.hpp" +#include "libtorrent/aux_/array.hpp" + +namespace libtorrent { +namespace aux { + +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/aux_/bandwidth_socket.hpp b/include/libtorrent/aux_/bandwidth_socket.hpp new file mode 100644 index 0000000..82cc30d --- /dev/null +++ b/include/libtorrent/aux_/bandwidth_socket.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2009, 2015, 2017-2020, Arvid Norberg +Copyright (c) 2016, 2020, 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_BANDWIDTH_SOCKET_HPP_INCLUDED +#define TORRENT_BANDWIDTH_SOCKET_HPP_INCLUDED + +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent { +namespace aux { + + 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/aux_/bind_to_device.hpp b/include/libtorrent/aux_/bind_to_device.hpp new file mode 100644 index 0000000..db18cfa --- /dev/null +++ b/include/libtorrent/aux_/bind_to_device.hpp @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2016, 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_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_/buffer.hpp b/include/libtorrent/aux_/buffer.hpp new file mode 100644 index 0000000..ea7e753 --- /dev/null +++ b/include/libtorrent/aux_/buffer.hpp @@ -0,0 +1,170 @@ +/* +Copyright (c) 2005, 2007, 2009, 2013-2020, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Tim Niederhausen +Copyright (c) 2019, Fabrice Fontaine +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + 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/aux_/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 __APPLE__ +#include +#endif + +namespace libtorrent { +namespace aux { + +// 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 __APPLE__ + 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/aux_/byteswap.hpp b/include/libtorrent/aux_/byteswap.hpp new file mode 100644 index 0000000..5d93b40 --- /dev/null +++ b/include/libtorrent/aux_/byteswap.hpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2010, 2014-2017, 2020, Arvid Norberg +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. + +*/ + +#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 swap_byteorder(std::uint32_t const x) +{ +#ifdef __GNUC__ + return __builtin_bswap32(x); +#else + return (x & 0xff000000) >> 24 + | (x & 0x00ff0000) >> 8 + | (x & 0x0000ff00) << 8 + | (x & 0x000000ff) << 24; +#endif +} + +inline std::uint32_t little_endian_to_host(std::uint32_t x) +{ +#if BOOST_ENDIAN_BIG_BYTE + return swap_byteorder(x); +#elif BOOST_ENDIAN_LITTLE_BYTE + return x; +#else +#error "unknown endian" +#endif +} + +} +} + +#endif // TORRENT_BYTESWAP_HPP_INCLUDED + diff --git a/include/libtorrent/aux_/chained_buffer.hpp b/include/libtorrent/aux_/chained_buffer.hpp new file mode 100644 index 0000000..5a6a769 --- /dev/null +++ b/include/libtorrent/aux_/chained_buffer.hpp @@ -0,0 +1,235 @@ +/* + +Copyright (c) 2007, 2011, 2013-2020, Arvid Norberg +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_CHAINED_BUFFER_HPP_INCLUDED +#define TORRENT_CHAINED_BUFFER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/aligned_storage.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/aux_/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 { +namespace aux { + + // 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/aux_/container_wrapper.hpp b/include/libtorrent/aux_/container_wrapper.hpp new file mode 100644 index 0000000..a9ac9e3 --- /dev/null +++ b/include/libtorrent/aux_/container_wrapper.hpp @@ -0,0 +1,136 @@ +/* + +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018-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_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 "libtorrent/aux_/numeric_cast.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; + // tested to fail with _MSC_VER <= 1916. The actual version condition +#if !defined _MSC_VER + constexpr +#endif + explicit container_wrapper(Base&& b) : Base(std::move(b)) {} + + explicit container_wrapper(IndexType const s) + : Base(numeric_cast(static_cast(s))) {} + + decltype(auto) operator[](IndexType idx) const + { + TORRENT_ASSERT(idx >= IndexType(0)); + TORRENT_ASSERT(idx < end_index()); + return this->Base::operator[](std::size_t(static_cast(idx))); + } + + decltype(auto) operator[](IndexType idx) + { + 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(numeric_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_/cpuid.hpp b/include/libtorrent/aux_/cpuid.hpp new file mode 100644 index 0000000..a8a9ed4 --- /dev/null +++ b/include/libtorrent/aux_/cpuid.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2010, 2014-2017, 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_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..57e7903 --- /dev/null +++ b/include/libtorrent/aux_/deferred_handler.hpp @@ -0,0 +1,81 @@ +/* + +Copyright (c) 2017, 2019-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_DEFERRED_HANDLER_HPP +#define TORRENT_DEFERRED_HANDLER_HPP + +#include "libtorrent/assert.hpp" +#include "libtorrent/io_context.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 allocator to the underlying handler's + using allocator_type = typename Handler::allocator_type; + + allocator_type get_allocator() const noexcept + { return m_handler.get_allocator(); } + +private: + Handler m_handler; + bool& m_in_flight; +}; + +struct deferred_handler +{ + template + void post_deferred(lt::io_context& ios, Handler&& h) + { + if (m_in_flight) return; + m_in_flight = true; + post(ios, 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..bd53e52 --- /dev/null +++ b/include/libtorrent/aux_/deprecated.hpp @@ -0,0 +1,68 @@ +/* + +Copyright (c) 2019-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_DEPRECATED_HPP_INCLUDED +#define TORRENT_DEPRECATED_HPP_INCLUDED + +#if !defined TORRENT_BUILDING_LIBRARY +# define TORRENT_DEPRECATED [[deprecated]] +#else +# define TORRENT_DEPRECATED +#endif + +#if defined __clang__ + +// ====== CLANG ======== + +# if !defined TORRENT_BUILDING_LIBRARY +// TODO: figure out which version of clang this is supported in +# define TORRENT_DEPRECATED_ENUM __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__ >= 6 && !defined TORRENT_BUILDING_LIBRARY +# define TORRENT_DEPRECATED_ENUM __attribute__ ((deprecated)) +# endif + +#endif + +#ifndef TORRENT_DEPRECATED_ENUM +#define TORRENT_DEPRECATED_ENUM +#endif + +#endif diff --git a/include/libtorrent/aux_/deque.hpp b/include/libtorrent/aux_/deque.hpp new file mode 100644 index 0000000..dc18a78 --- /dev/null +++ b/include/libtorrent/aux_/deque.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2017-2018, 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_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..ca312a3 --- /dev/null +++ b/include/libtorrent/aux_/dev_random.hpp @@ -0,0 +1,80 @@ +/* + +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2017, 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_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 + { + // the choice of /dev/urandom over /dev/random is based on: + // https://www.mail-archive.com/cryptography@randombit.net/msg04763.html + // https://security.stackexchange.com/questions/3936/is-a-rand-from-dev-urandom-secure-for-a-login-key/3939#3939 + dev_random() + : m_fd(::open("/dev/urandom", 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_/directory.hpp b/include/libtorrent/aux_/directory.hpp new file mode 100644 index 0000000..db34f28 --- /dev/null +++ b/include/libtorrent/aux_/directory.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2016-2017, Steven Siloti +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_DIRECTORY_HPP_INCLUDED +#define TORRENT_DIRECTORY_HPP_INCLUDED + +#include +#include "libtorrent/error_code.hpp" + +#ifdef TORRENT_WINDOWS +// windows part +#include "libtorrent/aux_/windows.hpp" +#include +#include +#else +// posix part + +#include // for DIR + +#endif +namespace libtorrent { +namespace aux { + +struct TORRENT_EXTRA_EXPORT directory +{ + directory(std::string const& path, error_code& ec); + ~directory(); + + directory(directory const&) = delete; + directory& operator=(directory const&) = delete; + + 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; +}; + + +} +} +#endif diff --git a/include/libtorrent/aux_/disable_deprecation_warnings_push.hpp b/include/libtorrent/aux_/disable_deprecation_warnings_push.hpp new file mode 100644 index 0000000..fb2079f --- /dev/null +++ b/include/libtorrent/aux_/disable_deprecation_warnings_push.hpp @@ -0,0 +1,51 @@ +/* + +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. + +*/ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#if __GNUC__ >= 9 +#pragma GCC diagnostic ignored "-Wdeprecated-copy" +#endif +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +#ifdef _MSC_VER +#pragma warning(push, 1) +#pragma warning(disable: 4996) +#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..4ee039e --- /dev/null +++ b/include/libtorrent/aux_/disable_warnings_push.hpp @@ -0,0 +1,113 @@ +/* + +Copyright (c) 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016-2017, 2019, 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. + +*/ + +#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 'result' used +#pragma warning(disable : 4701) +#endif diff --git a/include/libtorrent/aux_/disk_buffer_pool.hpp b/include/libtorrent/aux_/disk_buffer_pool.hpp new file mode 100644 index 0000000..350cdc8 --- /dev/null +++ b/include/libtorrent/aux_/disk_buffer_pool.hpp @@ -0,0 +1,132 @@ +/* + +Copyright (c) 2011, 2014-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016, 2020, 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_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_context.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/disk_buffer_holder.hpp" // for buffer_allocator_interface + +namespace libtorrent { + + struct settings_interface; + struct disk_observer; + +namespace aux { + + struct TORRENT_EXTRA_EXPORT disk_buffer_pool final + : buffer_allocator_interface + { + explicit disk_buffer_pool(io_context& ios); + ~disk_buffer_pool(); + disk_buffer_pool(disk_buffer_pool const&) = delete; + disk_buffer_pool& operator=(disk_buffer_pool const&) = delete; + + char* allocate_buffer(char const* category); + char* allocate_buffer(bool& exceeded, std::shared_ptr o + , char const* category); + void free_disk_buffer(char* b) override { free_buffer(b); } + void free_buffer(char* buf); + void free_multiple_buffers(span bufvec); + + int in_use() const + { + std::unique_lock l(m_pool_mutex); + return m_in_use; + } + + void set_settings(settings_interface const& sett); + + private: + + 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; + + // set to true to throttle more allocations + bool m_exceeded_max_size; + + // this is the main thread io_context. Callbacks are + // posted on this in order to have them execute in + // the main thread. + io_context& m_ios; + + 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 = 0x1337; + bool m_settings_set = false; +#endif + }; + +} +} + +#endif // TORRENT_DISK_BUFFER_POOL_HPP diff --git a/include/libtorrent/aux_/disk_io_thread_pool.hpp b/include/libtorrent/aux_/disk_io_thread_pool.hpp new file mode 100644 index 0000000..5f5a20b --- /dev/null +++ b/include/libtorrent/aux_/disk_io_thread_pool.hpp @@ -0,0 +1,150 @@ +/* + +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016, 2018, 2020, Alden Torres +Copyright (c) 2017-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_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_context.hpp" +#include "libtorrent/error_code.hpp" + +#include +#include +#include + +namespace libtorrent { +namespace aux { + + 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&, executor_work_guard) = 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_context& 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; + + io_context& m_ioc; + }; +} +} // namespace libtorrent + +#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..4be68a7 --- /dev/null +++ b/include/libtorrent/aux_/disk_job_fence.hpp @@ -0,0 +1,118 @@ +/* + +Copyright (c) 2016-2020, Arvid Norberg +Copyright (c) 2020, 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_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 counters; + +namespace aux { + + struct mmap_disk_job; + + // implements the disk I/O job fence used by the default_storage + // 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_EXTRA_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. + // fence_post_none if the fence job was queued. + enum { fence_post_fence = 0, fence_post_none = 1 }; + int raise_fence(mmap_disk_job*, counters&); + 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(mmap_disk_job*, tailqueue&); + 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(mmap_disk_job*); + + // 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 mmap_disk_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_/disk_job_pool.hpp b/include/libtorrent/aux_/disk_job_pool.hpp new file mode 100644 index 0000000..aa21a95 --- /dev/null +++ b/include/libtorrent/aux_/disk_job_pool.hpp @@ -0,0 +1,75 @@ +/* + +Copyright (c) 2010, 2013-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, 2020, 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_DISK_JOB_POOL +#define TORRENT_DISK_JOB_POOL + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/mmap_disk_job.hpp" // for job_action_t +#include "libtorrent/aux_/pool.hpp" +#include + +namespace libtorrent { +namespace aux { + + struct mmap_disk_job; + + struct TORRENT_EXTRA_EXPORT disk_job_pool + { + disk_job_pool(); + ~disk_job_pool(); + + mmap_disk_job* allocate_job(job_action_t type); + void free_job(mmap_disk_job* j); + void free_jobs(mmap_disk_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; + aux::pool m_job_pool; + }; +} +} + +#endif // TORRENT_DISK_JOB_POOL diff --git a/include/libtorrent/aux_/ed25519.hpp b/include/libtorrent/aux_/ed25519.hpp new file mode 100644 index 0000000..8f7f2db --- /dev/null +++ b/include/libtorrent/aux_/ed25519.hpp @@ -0,0 +1,18 @@ +#ifndef ED25519_HPP +#define ED25519_HPP + +#include "libtorrent/aux_/export.hpp" // for TORRENT_EXPORT +#include // for ptrdiff_t, size_t + +namespace libtorrent { +namespace aux { + +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/aux_/escape_string.hpp b/include/libtorrent/aux_/escape_string.hpp new file mode 100644 index 0000000..1630c02 --- /dev/null +++ b/include/libtorrent/aux_/escape_string.hpp @@ -0,0 +1,104 @@ +/* + +Copyright (c) 2004-2005, 2007, 2009, 2012-2018, 2020, Arvid Norberg +Copyright (c) 2015, Mikhail Titov +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_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); + + // 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); + + 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_NATIVE_UTF8 + inline std::string const& convert_to_native(std::string const& s) { return s; } + inline std::string const& convert_from_native(std::string const& s) { return s; } +#else + TORRENT_EXTRA_EXPORT std::string convert_to_native(std::string const& s); + TORRENT_EXTRA_EXPORT std::string convert_from_native(std::string const& 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..2cbac7d --- /dev/null +++ b/include/libtorrent/aux_/export.hpp @@ -0,0 +1,153 @@ +/* + +Copyright (c) 2014-2015, 2017-2020, Arvid Norberg +Copyright (c) 2019, Steven Siloti +Copyright (c) 2019, 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_EXPORT_HPP_INCLUDED +#define TORRENT_EXPORT_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/deprecated.hpp" + +#if !defined TORRENT_ABI_VERSION +# ifdef TORRENT_NO_DEPRECATE +# define TORRENT_ABI_VERSION 3 +# else +# define TORRENT_ABI_VERSION 1 +# endif +#endif + +#if TORRENT_ABI_VERSION >= 3 +# define TORRENT_VERSION_NAMESPACE_3 inline namespace v2 { +# define TORRENT_VERSION_NAMESPACE_3_END } +#else +# define TORRENT_VERSION_NAMESPACE_3 +# define TORRENT_VERSION_NAMESPACE_3_END +#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 + +#ifdef TORRENT_USE_LIBGCRYPT +# define TORRENT_CRYPTO_NAMESPACE inline namespace gcry { +# define TORRENT_CRYPTO_NAMESPACE_END } +#elif TORRENT_USE_COMMONCRYPTO +# define TORRENT_CRYPTO_NAMESPACE inline namespace cc { +# define TORRENT_CRYPTO_NAMESPACE_END } +#elif TORRENT_USE_CRYPTOAPI +# define TORRENT_CRYPTO_NAMESPACE inline namespace capi { +# define TORRENT_CRYPTO_NAMESPACE_END } +#elif defined TORRENT_USE_WOLFSSL +# define TORRENT_CRYPTO_NAMESPACE inline namespace wcrypto { +# define TORRENT_CRYPTO_NAMESPACE_END } +#elif defined TORRENT_USE_LIBCRYPTO +# define TORRENT_CRYPTO_NAMESPACE inline namespace lcrypto { +# define TORRENT_CRYPTO_NAMESPACE_END } +#else +# define TORRENT_CRYPTO_NAMESPACE inline namespace builtin { +# define TORRENT_CRYPTO_NAMESPACE_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 __GNUC__ >= 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_EXPORT_EXTRA \ + && ((defined __GNUC__ && __GNUC__ >= 4) || defined __clang__) +# define TORRENT_UNEXPORT __attribute__((visibility("hidden"))) +#else +# define TORRENT_UNEXPORT +#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 +// mingw doesn't like combining C++11 attributes with __attribute__ apparently +#if defined __MINGW64__ || defined __MINGW32__ + +# if TORRENT_ABI_VERSION >= 2 +# define TORRENT_DEPRECATED_EXPORT TORRENT_EXTRA_EXPORT +# else +# define TORRENT_DEPRECATED_EXPORT TORRENT_EXPORT +# endif + +#else + +# if TORRENT_ABI_VERSION >= 2 +# define TORRENT_DEPRECATED_EXPORT TORRENT_DEPRECATED TORRENT_EXTRA_EXPORT +# else +# define TORRENT_DEPRECATED_EXPORT TORRENT_DEPRECATED TORRENT_EXPORT +# endif + +#endif + +#endif + diff --git a/include/libtorrent/aux_/ffs.hpp b/include/libtorrent/aux_/ffs.hpp new file mode 100644 index 0000000..24e2951 --- /dev/null +++ b/include/libtorrent/aux_/ffs.hpp @@ -0,0 +1,71 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2017-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_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_pointer.hpp b/include/libtorrent/aux_/file_pointer.hpp new file mode 100644 index 0000000..3cff983 --- /dev/null +++ b/include/libtorrent/aux_/file_pointer.hpp @@ -0,0 +1,78 @@ +/* + +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_FILE_POINTER_HPP +#define TORRENT_FILE_POINTER_HPP + +#include +#include // for swap +#include + +#include "libtorrent/config.hpp" + +namespace libtorrent { +namespace aux { + +struct file_pointer +{ + file_pointer() : ptr(nullptr) {} + explicit file_pointer(FILE* p) : ptr(p) {} + ~file_pointer() { if (ptr != nullptr) ::fclose(ptr); } + file_pointer(file_pointer const&) = delete; + file_pointer(file_pointer&& f) : ptr(f.ptr) { f.ptr = nullptr; } + file_pointer& operator=(file_pointer const&) = delete; + file_pointer& operator=(file_pointer&& f) + { + std::swap(ptr, f.ptr); + return *this; + } + FILE* file() const { return ptr; } +private: + FILE* ptr; +}; + +inline int portable_fseeko(FILE* const f, std::int64_t const offset, int const whence) +{ +#ifdef TORRENT_WINDOWS + return ::_fseeki64(f, offset, whence); +#elif TORRENT_HAS_FSEEKO + return ::fseeko(f, offset, whence); +#else + int const fd = ::fileno(f); + return ::lseek64(fd, offset, whence) == -1 ? -1 : 0; +#endif +} + +} +} + +#endif diff --git a/include/libtorrent/aux_/file_progress.hpp b/include/libtorrent/aux_/file_progress.hpp new file mode 100644 index 0000000..00fc5c5 --- /dev/null +++ b/include/libtorrent/aux_/file_progress.hpp @@ -0,0 +1,109 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2019, 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_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/aux_/invariant_check.hpp" +#endif + +#if TORRENT_USE_INVARIANT_CHECKS +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/bitfield.hpp" +#endif + +namespace libtorrent { + +struct 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); + + std::int64_t total_on_disk() const + { + return m_total_on_disk; + } + + 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: + + // the total number of bytes downloaded to non-pad files + std::int64_t m_total_on_disk = 0; + + // 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 struct 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; + vector m_pad_file; +#endif + }; +} } + +#endif diff --git a/include/libtorrent/aux_/file_view_pool.hpp b/include/libtorrent/aux_/file_view_pool.hpp new file mode 100644 index 0000000..2bf722b --- /dev/null +++ b/include/libtorrent/aux_/file_view_pool.hpp @@ -0,0 +1,186 @@ +/* + +Copyright (c) 2006, 2009, 2013-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2019, 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_FILE_VIEW_POOL_HPP +#define TORRENT_FILE_VIEW_POOL_HPP + +#include "libtorrent/config.hpp" + +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + +#include +#include +#include +#include + +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/aux_/mmap.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#define BOOST_BIND_NO_PLACEHOLDERS + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + +class file_storage; +struct open_file_state; + +namespace aux { + + namespace mi = boost::multi_index; + + TORRENT_EXTRA_EXPORT file_open_mode_t to_file_open_mode(open_mode_t const); + + // this is an internal cache of open file mappings. + struct TORRENT_EXTRA_EXPORT file_view_pool + { + // ``size`` specifies the number of allowed files handles + // to hold open at any given time. + explicit file_view_pool(int size = 40); + ~file_view_pool(); + + file_view_pool(file_view_pool const&) = delete; + file_view_pool& operator=(file_view_pool const&) = delete; + + // 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_view open_file(storage_index_t st, std::string const& p + , file_index_t file_index, file_storage const& fs, open_mode_t m +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + , std::shared_ptr open_unmap_lock +#endif + ); + + // release all file views 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 views held + // by the file_view_pool. + int size_limit() const { return m_size; } + + std::vector get_status(storage_index_t st) const; + + void close_oldest(); + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + void flush_next_file(); + void record_file_write(storage_index_t st, file_index_t file_index + , uint64_t pages); +#endif + + private: + + std::shared_ptr remove_oldest(std::unique_lock&); + + int m_size; + + using file_id = std::pair; + + struct file_entry + { + file_entry(file_id k + , string_view name + , open_mode_t const m + , std::int64_t const size +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + , std::shared_ptr open_unmap_lock +#endif + ) + : key(k) + , mapping(std::make_shared(file_handle(name, size, m), m, size +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + , open_unmap_lock +#endif + )) + , mode(m) + {} + + file_id key; + std::shared_ptr mapping; + time_point last_use{aux::time_now()}; +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + std::uint64_t dirty_bytes; +#endif + open_mode_t mode{}; + }; + + using files_container = mi::multi_index_container< + file_entry, + mi::indexed_by< + // look up files by (torrent, file) key + mi::ordered_unique>, + // look up files by least recently used + mi::sequenced<> +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + // look up files with dirty pages + , mi::ordered_non_unique> +#endif + > + >; + + // maps storage pointer, file index pairs to the lru entry for the file + files_container m_files; + mutable std::mutex m_mutex; + + // the boost.multi-index container is not no-throw move constructable. In + // order to destruct m_files without holding the mutex, we need this + // separate pre-allocated container to move it into before releasing the + // mutex and clearing it. + files_container m_deferred_destruction; + mutable std::mutex m_destruction_mutex; + }; + +} +} + +#endif // HAVE_MMAP || HAVE_MAP_VIEW_OF_FILE + +#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..56a6151 --- /dev/null +++ b/include/libtorrent/aux_/generate_peer_id.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2018, 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_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..4b88101 --- /dev/null +++ b/include/libtorrent/aux_/has_block.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2018, 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_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_/hasher512.hpp b/include/libtorrent/aux_/hasher512.hpp new file mode 100644 index 0000000..5d5a53d --- /dev/null +++ b/include/libtorrent/aux_/hasher512.hpp @@ -0,0 +1,134 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2016-2017, 2019, Andrei Kurushin +Copyright (c) 2017-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_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_CNG +#include "libtorrent/aux_/win_cng.hpp" + +#elif TORRENT_USE_CRYPTOAPI_SHA_512 +#include "libtorrent/aux_/win_crypto_provider.hpp" + +#elif defined TORRENT_USE_LIBCRYPTO + + extern "C" { + #include + } + +#else +#include "libtorrent/aux_/sha512.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { +namespace aux { + + using sha512_hash = digest32<512>; + + // internal + struct TORRENT_EXTRA_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 + 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_CNG + aux::cng_hash 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/aux_/heterogeneous_queue.hpp b/include/libtorrent/aux_/heterogeneous_queue.hpp new file mode 100644 index 0000000..fe4550c --- /dev/null +++ b/include/libtorrent/aux_/heterogeneous_queue.hpp @@ -0,0 +1,260 @@ +/* + +Copyright (c) 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2018, 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_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); + } +} // namespace aux + + 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; + }; +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/aux_/instantiate_connection.hpp b/include/libtorrent/aux_/instantiate_connection.hpp new file mode 100644 index 0000000..d4756c9 --- /dev/null +++ b/include/libtorrent/aux_/instantiate_connection.hpp @@ -0,0 +1,58 @@ +/* + +Copyright (c) 2007, 2010, 2012, 2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2018, 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_INSTANTIATE_CONNECTION +#define TORRENT_INSTANTIATE_CONNECTION + +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/io_context.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 + aux::socket_type instantiate_connection(io_context& + , aux::proxy_settings const& ps + , void* ssl_context + , utp_socket_manager* sm + , bool peer_connection + , bool tracker_connection); +}} + +#endif diff --git a/include/libtorrent/aux_/invariant_check.hpp b/include/libtorrent/aux_/invariant_check.hpp new file mode 100644 index 0000000..cd854c5 --- /dev/null +++ b/include/libtorrent/aux_/invariant_check.hpp @@ -0,0 +1,86 @@ +// 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 { + + struct invariant_access + { + template + static void check_invariant(T const& self) + { + self.check_invariant(); + } + }; + +namespace aux { + + 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&& rhs) + : self(rhs.self), armed(rhs.armed) + { rhs.armed = false; } + + invariant_checker_impl(invariant_checker_impl const& rhs) = delete; + invariant_checker_impl& operator=(invariant_checker_impl const&) = delete; + + ~invariant_checker_impl() { if (armed) check_invariant(self); } + + T const& self; + bool armed = true; + }; + + template + invariant_checker_impl make_invariant_checker(T const& x) + { + return invariant_checker_impl(x); + } +} +} + +#define INVARIANT_CHECK \ + aux::invariant_checker const& _invariant_check = aux::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/aux_/io.hpp b/include/libtorrent/aux_/io.hpp new file mode 100644 index 0000000..fb45de7 --- /dev/null +++ b/include/libtorrent/aux_/io.hpp @@ -0,0 +1,180 @@ +/* + +Copyright (c) 2016-2018, 2020, Arvid Norberg +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_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_helpers.hpp b/include/libtorrent/aux_/ip_helpers.hpp new file mode 100644 index 0000000..82eef95 --- /dev/null +++ b/include/libtorrent/aux_/ip_helpers.hpp @@ -0,0 +1,68 @@ +/* + +Copyright (c) 2015-2017, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +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_IP_HELPERS_HPP_INCLUDED +#define TORRENT_IP_HELPERS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent { +namespace aux { + + 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_teredo(address const& addr); + TORRENT_EXTRA_EXPORT bool is_ip_address(std::string const& host); + + // internal + 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(); + } + + TORRENT_EXTRA_EXPORT address ensure_v6(address const& a); + +} +} + +#endif diff --git a/include/libtorrent/aux_/ip_notifier.hpp b/include/libtorrent/aux_/ip_notifier.hpp new file mode 100644 index 0000000..b359e8b --- /dev/null +++ b/include/libtorrent/aux_/ip_notifier.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, 2019-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_IP_NOTIFIER_HPP_INCLUDED +#define TORRENT_IP_NOTIFIER_HPP_INCLUDED + +#include +#include + +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_context.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_context& 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..ddef916 --- /dev/null +++ b/include/libtorrent/aux_/listen_socket_handle.hpp @@ -0,0 +1,92 @@ +/* + +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, Alden Torres +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_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; + listen_socket_handle(listen_socket_handle&& o) = default; + listen_socket_handle& operator=(listen_socket_handle&& 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..9d7b0cf --- /dev/null +++ b/include/libtorrent/aux_/lsd.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, 2019-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 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..3832d26 --- /dev/null +++ b/include/libtorrent/aux_/merkle.hpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2015, 2017, 2019-2020, Arvid Norberg +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_MERKLE_HPP_INCLUDED +#define TORRENT_MERKLE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/aux_/vector.hpp" +#include +#include // pair + +namespace libtorrent { + + struct bitfield; + + // given layer and offset from the start of the layer, return the nodex + // index + TORRENT_EXTRA_EXPORT int merkle_to_flat_index(int layer, int offset); + + // given layer, returns index to the layer's first node + TORRENT_EXTRA_EXPORT int merkle_layer_start(int layer); + + // given the number of blocks, how many leaves do we need? this rounds up to + // an even power of 2 + TORRENT_EXTRA_EXPORT int merkle_num_leafs(int); + + // returns the number of nodes in the tree, given the number of leaves + TORRENT_EXTRA_EXPORT int merkle_num_nodes(int); + + // given the number of leafs in the tree, returns the index to the first + // leaf + TORRENT_EXTRA_EXPORT int merkle_first_leaf(int num_leafs); + + // takes the number of leaves and returns the height of the merkle tree. + // does not include the root node in the layer count. i.e. if there's only a + // root hash, there are 0 layers. Note that the number of leaves must be + // valid, i.e. a power of 2. + TORRENT_EXTRA_EXPORT int merkle_num_layers(int); + TORRENT_EXTRA_EXPORT int merkle_get_parent(int); + TORRENT_EXTRA_EXPORT int merkle_get_sibling(int); + TORRENT_EXTRA_EXPORT int merkle_get_first_child(int); + TORRENT_EXTRA_EXPORT int merkle_get_first_child(int tree_node, int depth); + + // given a tree and the number of leaves, expect all leaf hashes to be set and + // compute all other hashes starting with the leaves. + TORRENT_EXTRA_EXPORT void merkle_fill_tree(span tree, int num_leafs, int level_start); + TORRENT_EXTRA_EXPORT void merkle_fill_tree(span tree, int num_leafs); + + // fills in nodes that can be computed from a tree with arbitrary nodes set + // all "orphan" hashes, i.e ones that do not contribute towards computing + // the root, will be cleared. + TORRENT_EXTRA_EXPORT void merkle_fill_partial_tree(span tree); + + // given a merkle tree (`tree`), clears all hashes in the range of nodes: + // [ level_start, level_start+ num_leafs), as well as all of their parents, + // within the sub-tree. It does not clear the root of the sub-tree. + // see unit test for examples. + TORRENT_EXTRA_EXPORT void merkle_clear_tree(span tree, int num_leafs, int level_start); + + // given the leaf hashes, computes the merkle root hash. The pad is the hash + // to use for the right-side padding, in case the number of leaves is not a + // power of two. + TORRENT_EXTRA_EXPORT sha256_hash merkle_root(span leaves, sha256_hash const& pad = {}); + + TORRENT_EXTRA_EXPORT + sha256_hash merkle_root_scratch(span leaves, int num_leafs + , sha256_hash pad, std::vector& scratch_space); + + // given a flat index, return which layer the node is in + TORRENT_EXTRA_EXPORT int merkle_get_layer(int idx); + // given a flat index, return the offset in the layer + TORRENT_EXTRA_EXPORT int merkle_get_layer_offset(int idx); + + // given "blocks" number of leafs in the full tree (i.e. at the block level) + // and given "pieces" nodes in the piece layer, compute the pad hash for the + // piece layer + TORRENT_EXTRA_EXPORT sha256_hash merkle_pad(int blocks, int pieces); + + // validates and inserts the uncle hashes (and the specified node) into the + // target tree. "node" is the hash at target_node_idx and uncle_hashes are + // the uncle hashes all the way up to the root of target_tree, to prove "node" + // is valid. The hashes are only inserted into target_tree if they validate. + // returns true if all hashes validated correctly, and false otherwise. + // + // For example, consider the following tree (target_tree): + // + // R + // 2 _ + // _ _ _ 1 + //_ _ _ _ N 0 _ _ + // The root R is expected to be known and set in target_tree. + // if we're inserting the hash N, the uncle hashes provide proof of it being + // valid by containing 0, 1 and two (as marked in the tree above) + // Any non-zero hash encountered in target_tree is assumed to be valid, and + // will termiate the validation early, either successful (if there's a + // match) or unsuccessful (if there's a mismatch). + TORRENT_EXTRA_EXPORT + bool merkle_validate_and_insert_proofs(span target_tree + , int target_node_idx, sha256_hash const& node, span uncle_hashes); + + TORRENT_EXTRA_EXPORT + bool merkle_validate_node(sha256_hash const& left, sha256_hash const& right + , sha256_hash const& parent); + + // validates hashes from src and copies the valid ones to dst given root as + // the expected root of the tree (i.e. index 0) + // src and dst must be the same size. dst is expected to be initialized + // cleared (or only have valid hashes set), this function will not clear + // hashes in dst that are invalid in src. + TORRENT_EXTRA_EXPORT + void merkle_validate_copy(span src, span dst + , sha256_hash const& root, bitfield& verified_leafs); + + TORRENT_EXTRA_EXPORT + bool merkle_validate_single_layer(span tree); + + // given a leaf index (0-based index in the leaf layer) and a tree, return + // the leafs_start, leafs_size and root_index representing a subtree that + // can be validated. The block_index and leaf_size is the range of the leaf + // layer that can be verified, and the root_index is the node that needs to + // be known in (tree) to do so. The num_valid_leafs specifies how many of + // the leafs that are actually *supposed* to be non-zero. Any leafs beyond + // thses are padding and expected to be zero. + // The caller must validate the hash at root_index. + TORRENT_EXTRA_EXPORT + std::tuple merkle_find_known_subtree(span const tree + , int block_index, int num_valid_leafs); +} + +#endif diff --git a/include/libtorrent/aux_/merkle_tree.hpp b/include/libtorrent/aux_/merkle_tree.hpp new file mode 100644 index 0000000..d5bfc50 --- /dev/null +++ b/include/libtorrent/aux_/merkle_tree.hpp @@ -0,0 +1,226 @@ +/* + +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_MERKLE_TREE_HPP_INCLUDED +#define TORRENT_MERKLE_TREE_HPP_INCLUDED + +#include +#include +#include // for pair +#include + +#include "libtorrent/sha1_hash.hpp" // for sha256_hash +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/optional.hpp" +#include "libtorrent/bitfield.hpp" +#if TORRENT_USE_INVARIANT_CHECKS +#include "libtorrent/aux_/invariant_check.hpp" +#endif + +namespace libtorrent { +namespace aux { + +struct add_hashes_result_t +{ + std::vector passed; + std::vector>> failed; +}; + +// represents the merkle tree for files belonging to a torrent. +// Each file has a root-hash and a "piece layer", i.e. the level in the tree +// representing whole pieces. Those hashes are likely to be included in .torrent +// files and known up-front. + +// The invariant of the tree is that all interior nodes (i.e. all but the very +// bottom leaf nodes, representing block hashes) are either set and valid, or +// clear. No invalid hashes are allowed, and they can only be added by also +// providing proof of being valid. + +// The leaf blocks on the other hand, MAY be invalid. For instance, when adding +// a magnet link for a torrent that we already have files for. Once we have the +// metadata, we have files on disk but no hashes. We won't know whether the data +// on disk is valid or not, until we've downloaded the hashes to validate them. + +// Idea for future space optimization: +// while downloading, we need to store interior nodes of this tree. However, we +// don't need to store the padding. a SHA-256 is 32 bytes. Instead of storing +// the full (padded) tree of SHA-256 hashes, store the full tree of 32 bit +// signed integers, being indices into the actual storage for the tree. We could +// even grow the storage lazily. Instead of storing the padding hashes, use +// negative indices to refer to fixed SHA-256(0), and SHA-256(SHA-256(0)) and so +// on +struct TORRENT_EXTRA_EXPORT merkle_tree +{ + // TODO: remove this constructor. Don't support "uninitialized" trees. This + // also requires not constructing these for pad-files and small files as + // well. So, a sparse hash list in torrent_info + merkle_tree() = default; + merkle_tree(int num_blocks, int blocks_per_piece, char const* r); + + sha256_hash root() const; + + void load_tree(span t, std::vector const& verified); + void load_sparse_tree(span t, std::vector const& mask + , std::vector const& verified); + void load_verified_bits(std::vector const& verified); + + std::size_t size() const; + int end_index() const { return int(size()); } + + bool has_node(int idx) const; + + bool compare_node(int idx, sha256_hash const& h) const; + + sha256_hash operator[](int idx) const; + + std::vector build_vector() const; + std::pair, aux::vector> build_sparse_vector() const; + + // get bits indicating if each leaf hash is verified + std::vector verified_leafs() const; + + // returns true if the entire tree is known and verified + bool is_complete() const; + + // returns true if all block hashes in the specified range have been verified + bool blocks_verified(int block_idx, int num_blocks) const; + + bool load_piece_layer(span piece_layer); + + // the leafs in "tree" must be block hashes (i.e. leaf hashes in the this + // tree). This function inserts those hashes as well as the nodes up the + // tree. The destination start index is the index, in this tree, to the first leaf + // where "tree" will be inserted. + // inserts the nodes in "proofs" as a path up the tree. The proofs are + // sibling hashes, as they are returned from add_hashes(). The hashes must + // be valid. + // if the hashes are not valid, or the uncle hashes fail validation, nullopt + // is returned. + boost::optional add_hashes( + int dest_start_idx + , piece_index_t::diff_type file_piece_offset + , span hashes + , span uncle_hashes); + + aux::vector get_piece_layer() const; + + enum class set_block_result + { + ok, unknown, hash_failed, block_hash_failed + }; + + std::tuple set_block(int block_index + , sha256_hash const& h); + + std::vector get_hashes(int base + , int index, int count, int proof_layers) const; + +private: + + // set to an empty tree + void clear(); + + sha256_hash get_impl(int idx, std::vector& scratch_space) const; + + int blocks_per_piece() const { return 1 << m_blocks_per_piece_log; } + // the number tree levels per piece. This is 0 if the block layer is also + // the piece layer. + int piece_levels() const { return m_blocks_per_piece_log; } + + int block_layer_start() const; + int piece_layer_start() const; + int num_pieces() const; + int num_leafs() const; + + void optimize_storage(); + void optimize_storage_piece_layer(); + void allocate_full(); + + // a pointer to the root hash for this file. + char const* m_root = nullptr; + + // this is either the full tree, or some sparse representation of it, + // depending on m_mode + // TODO: make this a std::unique_ptr + aux::vector m_tree; + + // when the full tree is allocated, this has one bit for each block hash. a + // 1 means we have verified the block hash to be corret, otherwise the block + // hash may represent what's on disk, but we haven't been able to verify it + // yet + bitfield m_block_verified; + + // number of blocks in the file this tree represents. The number of leafs in + // the tree is rounded up to an even power of 2. + int m_num_blocks = 0; + + // the number of blocks per piece, specified as how many steps to shift + // right 1 to get the number of blocks in one piece. This is a compact + // representation that's valid because pieces are always powers of 2. + // this is necessary to know which layer in the tree the piece layer is. + std::uint8_t m_blocks_per_piece_log = 0; + + enum class mode_t : std::uint8_t + { + // a default constructed tree is truly empty. It does not even have a + // root hash + uninitialized_tree, + + // we don't have any hashes in this tree. m_tree should be empty + // an empty tree still always have the root hash (available as root()) + empty_tree, + + // in this mode, m_tree represents the full tree, including padding. + full_tree, + + // in this mode, m_tree represents the piece layer only, no padding + // and all piece layer hashes are stored and valid + piece_layer, + + // in this mode, m_tree represents the block (leaf) layer only, no padding + // and all block layer hashes are stored and valid + block_layer + }; + mode_t m_mode = mode_t::uninitialized_tree; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; + friend struct libtorrent::invariant_access; +#endif +}; + +} +} + +#endif diff --git a/include/libtorrent/aux_/mmap.hpp b/include/libtorrent/aux_/mmap.hpp new file mode 100644 index 0000000..de862d0 --- /dev/null +++ b/include/libtorrent/aux_/mmap.hpp @@ -0,0 +1,210 @@ +/* + +Copyright (c) 2016, 2018-2020, Arvid Norberg +Copyright (c) 2019, 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_MMAP_HPP +#define TORRENT_MMAP_HPP + +#include "libtorrent/config.hpp" + +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + +#include "libtorrent/disk_interface.hpp" // for open_file_state +#include "libtorrent/aux_/open_mode.hpp" + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + +#include "libtorrent/aux_/windows.hpp" +#include + +#endif // TORRENT_HAVE_MAP_VIEW_OF_FILE + +namespace libtorrent { + +// for now +using byte = char; + +namespace aux { + + using namespace flags; + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + using native_handle_t = HANDLE; + const native_handle_t invalid_handle = INVALID_HANDLE_VALUE; +#else + using native_handle_t = int; + const native_handle_t invalid_handle = -1; +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + struct TORRENT_EXTRA_EXPORT file_handle + { + file_handle(string_view name, std::int64_t size, open_mode_t mode); + file_handle(file_handle const& rhs) = delete; + file_handle& operator=(file_handle const& rhs) = delete; + + file_handle(file_handle&& rhs) : m_fd(rhs.m_fd) { rhs.m_fd = invalid_handle; } + file_handle& operator=(file_handle&& rhs) &; + + ~file_handle(); + + std::int64_t get_size() const; + + native_handle_t fd() const { return m_fd; } + private: + void close(); + native_handle_t m_fd; +#ifdef TORRENT_WINDOWS + aux::open_mode_t m_open_mode; +#endif + }; + + struct file_view; + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + struct TORRENT_EXTRA_EXPORT file_mapping_handle + { + file_mapping_handle(file_handle file, open_mode_t mode, std::int64_t size); + ~file_mapping_handle(); + file_mapping_handle(file_mapping_handle const&) = delete; + file_mapping_handle& operator=(file_mapping_handle const&) = delete; + + file_mapping_handle(file_mapping_handle&& fm); + file_mapping_handle& operator=(file_mapping_handle&& fm) &; + + HANDLE handle() const { return m_mapping; } + private: + void close(); + file_handle m_file; + HANDLE m_mapping; + }; +#endif + + struct TORRENT_EXTRA_EXPORT file_mapping : std::enable_shared_from_this + { + friend struct file_view; + + file_mapping(file_handle file, open_mode_t mode, std::int64_t file_size +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + , std::shared_ptr open_unmap_lock +#endif + ); + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + void flush(); +#endif + + // non-copyable + file_mapping(file_mapping const&) = delete; + file_mapping& operator=(file_mapping const&) = delete; + + file_mapping(file_mapping&& rhs); + file_mapping& operator=(file_mapping&& rhs) &; + ~file_mapping(); + + // ... + file_view view(); + private: + + void close(); + + // the memory range this file has been mapped into + span memory() + { + TORRENT_ASSERT(m_mapping || m_size == 0); + return { static_cast(m_mapping), static_cast(m_size) }; + } + + // hint the kernel that we probably won't need this part of the file + // anytime soon + void dont_need(span range); + + // hint the kernel that the given (dirty) range of pages should be + // flushed to disk + void page_out(span range); + + std::int64_t m_size; +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + file_mapping_handle m_file; + std::shared_ptr m_open_unmap_lock; +#else + file_handle m_file; +#endif + void* m_mapping; + }; + + struct TORRENT_EXTRA_EXPORT file_view + { + friend struct file_mapping; + file_view(file_view&&) = default; + file_view& operator=(file_view&&) = default; + + span range() const + { + TORRENT_ASSERT(m_mapping); + return m_mapping->memory(); + } + + span range() + { + TORRENT_ASSERT(m_mapping); + return m_mapping->memory(); + } + + void dont_need(span range) + { + TORRENT_ASSERT(m_mapping); + m_mapping->dont_need(range); + } + + void page_out(span range) + { + TORRENT_ASSERT(m_mapping); + m_mapping->page_out(range); + } + + + private: + explicit file_view(std::shared_ptr m) : m_mapping(std::move(m)) {} + std::shared_ptr m_mapping; + }; + +} // aux +} // libtorrent + +#endif // HAVE_MMAP || HAVE_MAP_VIEW_OF_FILE + +#endif + diff --git a/include/libtorrent/aux_/mmap_disk_job.hpp b/include/libtorrent/aux_/mmap_disk_job.hpp new file mode 100644 index 0000000..36775e6 --- /dev/null +++ b/include/libtorrent/aux_/mmap_disk_job.hpp @@ -0,0 +1,233 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2017-2018, 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_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 mmap_storage; + +namespace aux { + + // internal + enum class job_action_t : std::uint8_t + { + read + , write + , hash + , hash2 + , move_storage + , release_files + , delete_files + , check_fastresume + , rename_file + , stop_torrent + , file_priority + , clear_piece + , partial_read + , 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 mmap_disk_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 mmap_disk_job : tailqueue_node + { + mmap_disk_job(); + mmap_disk_job(mmap_disk_job const&) = delete; + mmap_disk_job& operator=(mmap_disk_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 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 hash2_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 + struct hash_args + { + sha1_hash piece_hash; + span block_hashes; + } h; + sha256_hash piece_hash2; + + // 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; + + // this is used for partial_read. It's the number of bytes to skip + // into the buffer that we're reading into. + std::uint16_t buffer_offset; + + } 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/aux_/noexcept_movable.hpp b/include/libtorrent/aux_/noexcept_movable.hpp new file mode 100644 index 0000000..4f593cd --- /dev/null +++ b/include/libtorrent/aux_/noexcept_movable.hpp @@ -0,0 +1,112 @@ +/* + +Copyright (c) 2017-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_NOEXCEPT_MOVABLE_HPP_INCLUDED +#define TORRENT_NOEXCEPT_MOVABLE_HPP_INCLUDED + +#include +#include + +namespace libtorrent { +namespace aux { + +#if defined _MSC_VER && defined TORRENT_BUILD_SIMULATOR + // see simulation/test_error_handling.cpp for a description of this variable + extern thread_local int g_must_not_fail; + + template + T&& wrap(T&& v) { + ++g_must_not_fail; + return v; + } +#endif + + // 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; +#if defined _MSC_VER && defined TORRENT_BUILD_SIMULATOR + noexcept_movable(noexcept_movable&& rhs) noexcept + : T(std::forward(wrap(rhs))) + { + --g_must_not_fail; + } + noexcept_movable(T&& rhs) noexcept : T(std::forward(wrap(rhs))) // NOLINT + { + --g_must_not_fail; + } +#else + noexcept_movable(noexcept_movable&& rhs) noexcept + : T(std::forward(rhs)) {} + noexcept_movable(T&& rhs) noexcept : T(std::forward(rhs)) {} // NOLINT +#endif + noexcept_movable(noexcept_movable const& rhs) = default; + 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=; + }; + + template + struct noexcept_move_only : T + { +#if defined _MSC_VER && defined TORRENT_BUILD_SIMULATOR + noexcept_move_only(noexcept_move_only&& rhs) noexcept + : T(std::forward(wrap(rhs))) + { + --g_must_not_fail; + } + noexcept_move_only(T&& rhs) noexcept : T(std::forward(wrap(rhs))) // NOLINT + { + --g_must_not_fail; + } +#else + noexcept_move_only(noexcept_move_only&& rhs) noexcept + : T(std::forward(rhs)) + {} + noexcept_move_only(T&& rhs) noexcept : T(std::forward(rhs)) {} // NOLINT +#endif + noexcept_move_only(noexcept_move_only const& rhs) = default; + noexcept_move_only(T const& rhs) : T(rhs) {} // NOLINT + noexcept_move_only& operator=(noexcept_move_only const& rhs) = default; + noexcept_move_only& operator=(noexcept_move_only&& rhs) = default; + using T::T; + }; + +} +} + +#endif diff --git a/include/libtorrent/aux_/numeric_cast.hpp b/include/libtorrent/aux_/numeric_cast.hpp new file mode 100644 index 0000000..1a115d1 --- /dev/null +++ b/include/libtorrent/aux_/numeric_cast.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2017, Alden Torres +Copyright (c) 2018, Steven Siloti +Copyright (c) 2018, 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_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_/open_mode.hpp b/include/libtorrent/aux_/open_mode.hpp new file mode 100644 index 0000000..2c78365 --- /dev/null +++ b/include/libtorrent/aux_/open_mode.hpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2016-2017, 2019-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_OPEN_MODE_HPP +#define TORRENT_OPEN_MODE_HPP + +#include +#include "libtorrent/flags.hpp" + +namespace libtorrent { +namespace aux { + + // hidden + using open_mode_t = flags::bitfield_flag; + + namespace open_mode { + constexpr open_mode_t read_only{0}; + constexpr open_mode_t write = 0_bit; + constexpr open_mode_t no_cache = 1_bit; + constexpr open_mode_t truncate = 2_bit; + constexpr open_mode_t no_atime = 3_bit; + constexpr open_mode_t random_access = 4_bit; + constexpr open_mode_t hidden = 5_bit; + constexpr open_mode_t sparse = 6_bit; + constexpr open_mode_t executable = 7_bit; + constexpr open_mode_t allow_set_file_valid_data = 8_bit; + } +} // aux + +} // libtorrent + +#endif + diff --git a/include/libtorrent/aux_/packet_buffer.hpp b/include/libtorrent/aux_/packet_buffer.hpp new file mode 100644 index 0000000..f7ff6c5 --- /dev/null +++ b/include/libtorrent/aux_/packet_buffer.hpp @@ -0,0 +1,122 @@ +/* + +Copyright (c) 2010-2018, Arvid Norberg, Daniel Wallin. +Copyright (c) 2010-2012, 2014-2020, Arvid Norberg +Copyright (c) 2017, 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_PACKET_BUFFER_HPP_INCLUDED +#define TORRENT_PACKET_BUFFER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/unique_ptr.hpp" +#include "libtorrent/aux_/packet_pool.hpp" // for packet_ptr/packet_deleter +#include +#include +#include // for unique_ptr + +namespace libtorrent { +namespace aux { + + 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/aux_/packet_pool.hpp b/include/libtorrent/aux_/packet_pool.hpp new file mode 100644 index 0000000..1d91857 --- /dev/null +++ b/include/libtorrent/aux_/packet_pool.hpp @@ -0,0 +1,226 @@ +/* + +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2017, 2019-2020, Arvid Norberg +Copyright (c) 2017-2018, 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_PACKET_POOL_HPP +#define TORRENT_PACKET_POOL_HPP + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/debug.hpp" // for single_threaded + +#include +#include // for unique_ptr +#include + +namespace libtorrent { +namespace aux { + + // 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/aux_/path.hpp b/include/libtorrent/aux_/path.hpp new file mode 100644 index 0000000..cafb372 --- /dev/null +++ b/include/libtorrent/aux_/path.hpp @@ -0,0 +1,187 @@ +/* + +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017-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_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" + +#ifdef TORRENT_WINDOWS +// windows part +#include "libtorrent/aux_/windows.hpp" +#include +#include +#else + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#include +#include +#include +#include +#include // for DIR + +#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 is_directory(std::string const& f + , 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 path_equal(std::string const& lhs, std::string const& rhs); + + // compare each path element individually + TORRENT_EXTRA_EXPORT int path_compare(string_view lhs, string_view lfile + , string_view rhs, string_view rfile); + + // 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_/polymorphic_socket.hpp b/include/libtorrent/aux_/polymorphic_socket.hpp new file mode 100644 index 0000000..0c71a14 --- /dev/null +++ b/include/libtorrent/aux_/polymorphic_socket.hpp @@ -0,0 +1,188 @@ +/* + +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_POLYMORPHIC_SOCKET +#define TORRENT_POLYMORPHIC_SOCKET + +#include "libtorrent/error_code.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { +namespace aux { + + template + struct first_element + { using type = T; }; + +#define TORRENT_FWD_CALL(x) \ + return boost::apply_visitor([&](auto& s){ return s.x; }, *this) + + template + struct polymorphic_socket + : boost::variant + { + using first_socket = typename first_element::type; + using endpoint_type = typename first_socket::endpoint_type; + using protocol_type = typename first_socket::protocol_type; + + using receive_buffer_size = typename first_socket::receive_buffer_size; + using send_buffer_size = typename first_socket::send_buffer_size; + + using executor_type = typename first_socket::executor_type; + + template + explicit polymorphic_socket(S s) : boost::variant(std::move(s)) + { + static_assert(std::is_nothrow_move_constructible::value + , "should really be nothrow move contsructible, since it's part of a variant"); + } + polymorphic_socket(polymorphic_socket&&) = default; + ~polymorphic_socket() = default; + + bool is_open() const + { TORRENT_FWD_CALL(is_open()); } + + void open(protocol_type const& p, error_code& ec) + { TORRENT_FWD_CALL(open(p, ec)); } + + void close(error_code& ec) + { TORRENT_FWD_CALL(close(ec)); } + + endpoint_type local_endpoint(error_code& ec) const + { TORRENT_FWD_CALL(local_endpoint(ec)); } + + endpoint_type remote_endpoint(error_code& ec) const + { TORRENT_FWD_CALL(remote_endpoint(ec)); } + + void bind(endpoint_type const& endpoint, error_code& ec) + { TORRENT_FWD_CALL(bind(endpoint, ec)); } + + std::size_t available(error_code& ec) const + { TORRENT_FWD_CALL(available(ec)); } + +#ifndef BOOST_NO_EXCEPTIONS + void open(protocol_type const& p) + { TORRENT_FWD_CALL(open(p)); } + + void close() + { TORRENT_FWD_CALL(close()); } + + endpoint_type local_endpoint() const + { TORRENT_FWD_CALL(local_endpoint()); } + + endpoint_type remote_endpoint() const + { TORRENT_FWD_CALL(remote_endpoint()); } + + void bind(endpoint_type const& endpoint) + { TORRENT_FWD_CALL(bind(endpoint)); } + + std::size_t available() const + { TORRENT_FWD_CALL(available()); } +#endif + + template + std::size_t read_some(Mutable_Buffers const& buffers, error_code& ec) + { TORRENT_FWD_CALL(read_some(buffers, ec)); } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler handler) + { TORRENT_FWD_CALL(async_read_some(buffers, std::move(handler))); } + + template + std::size_t write_some(Const_Buffers const& buffers, error_code& ec) + { TORRENT_FWD_CALL(write_some(buffers, ec)); } + + template + void async_write_some(Const_Buffers const& buffers, Handler handler) + { TORRENT_FWD_CALL(async_write_some(buffers, std::move(handler))); } + + template + void async_connect(endpoint_type const& endpoint, Handler handler) + { TORRENT_FWD_CALL(async_connect(endpoint, std::move(handler))); } + +#ifndef BOOST_NO_EXCEPTIONS + template + void io_control(IO_Control_Command& ioc) + { TORRENT_FWD_CALL(io_control(ioc)); } + + template + std::size_t read_some(Mutable_Buffers const& buffers) + { TORRENT_FWD_CALL(read_some(buffers)); } +#endif + + template + void io_control(IO_Control_Command& ioc, error_code& ec) + { TORRENT_FWD_CALL(io_control(ioc, ec)); } + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const& opt) + { TORRENT_FWD_CALL(set_option(opt)); } +#endif + + template + void set_option(SettableSocketOption const& opt, error_code& ec) + { TORRENT_FWD_CALL(set_option(opt, ec)); } + + void non_blocking(bool b, error_code& ec) + { TORRENT_FWD_CALL(non_blocking(b, ec)); } + +#ifndef BOOST_NO_EXCEPTIONS + void non_blocking(bool b) + { TORRENT_FWD_CALL(non_blocking(b)); } +#endif + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption& opt) + { TORRENT_FWD_CALL(get_option(opt)); } +#endif + + template + void get_option(GettableSocketOption& opt, error_code& ec) + { TORRENT_FWD_CALL(get_option(opt, ec)); } + + // explicitly disallow assignment, to silence msvc warning + polymorphic_socket& operator=(polymorphic_socket const&) = delete; + polymorphic_socket& operator=(polymorphic_socket&&) = default; + }; + +#undef TORRENT_FWD_CALL + +} +} + +#endif // TORRENT_POLYMORPHIC_SOCKET diff --git a/include/libtorrent/aux_/pool.hpp b/include/libtorrent/aux_/pool.hpp new file mode 100644 index 0000000..043c1b2 --- /dev/null +++ b/include/libtorrent/aux_/pool.hpp @@ -0,0 +1,60 @@ +/* + +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_POOL_HPP +#define TORRENT_POOL_HPP + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { +namespace aux { + +struct allocator_new_delete +{ + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + // TODO: ensure the alignment is good here + static char* malloc(size_type const bytes) + { return new char[bytes]; } + static void free(char* const block) + { delete [] block; } +}; + +using pool = boost::pool; + +} +} + +#endif diff --git a/include/libtorrent/aux_/portmap.hpp b/include/libtorrent/aux_/portmap.hpp new file mode 100644 index 0000000..34c7d0b --- /dev/null +++ b/include/libtorrent/aux_/portmap.hpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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" +#include "libtorrent/aux_/listen_socket_handle.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 + , listen_socket_handle const& ls) = 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 + , listen_socket_handle const&) 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_/posix_part_file.hpp b/include/libtorrent/aux_/posix_part_file.hpp new file mode 100644 index 0000000..6b54864 --- /dev/null +++ b/include/libtorrent/aux_/posix_part_file.hpp @@ -0,0 +1,139 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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_POSIX_PART_FILE_HPP_INCLUDE +#define TORRENT_POSIX_PART_FILE_HPP_INCLUDE + +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/aux_/open_mode.hpp" +#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t +#include "libtorrent/aux_/file_pointer.hpp" + +namespace libtorrent { +namespace aux { + + using slot_index_t = aux::strong_typedef; + + struct TORRENT_EXTRA_EXPORT posix_part_file + { + // create a part file at ``path``, that can hold ``num_pieces`` pieces. + // each piece being ``piece_size`` number of bytes + posix_part_file(std::string path, std::string name, int num_pieces, int piece_size); + ~posix_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); + int hashv(hasher& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec); + int hashv2(hasher256& ph, std::ptrdiff_t len, 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 posix_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: + + enum class open_mode : std::uint8_t + { + read_only, read_write + }; + + file_pointer open_file(open_mode 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; + } + + template + int do_hashv(Hasher& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec); + + 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 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 posix_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/aux_/posix_storage.hpp b/include/libtorrent/aux_/posix_storage.hpp new file mode 100644 index 0000000..33cfc26 --- /dev/null +++ b/include/libtorrent/aux_/posix_storage.hpp @@ -0,0 +1,122 @@ +/* + +Copyright (c) 2016, 2019-2020, Arvid Norberg +Copyright (c) 2018, 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_POSIX_STORAGE +#define TORRENT_POSIX_STORAGE + +#include "libtorrent/config.hpp" +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t +#include "libtorrent/hex.hpp" // to_hex +#include "libtorrent/aux_/open_mode.hpp" // for aux::open_mode_t +#include "libtorrent/aux_/file_pointer.hpp" +#include "libtorrent/aux_/posix_part_file.hpp" +#include +#include + +namespace libtorrent { +namespace aux { + + struct session_settings; + + struct TORRENT_EXTRA_EXPORT posix_storage + { + explicit posix_storage(storage_params const& p); + file_storage const& files() const; + file_storage const& orig_files() const { return m_files; } + ~posix_storage(); + + int readv(settings_interface const& sett + , span bufs + , piece_index_t const piece, int const offset + , storage_error& error); + + int writev(settings_interface const& sett + , span bufs + , piece_index_t const piece, int const offset + , storage_error& error); + + bool has_any_file(storage_error& error); + void set_file_priority(aux::vector& prio + , storage_error& ec); + bool verify_resume_data(add_torrent_params const& rd + , aux::vector const& links + , storage_error& ec); + + void release_files(); + + void delete_files(remove_flags_t options, storage_error& error); + + std::pair move_storage(std::string const& sp + , move_flags_t const flags, storage_error& ec); + + void rename_file(file_index_t const index, std::string const& new_filename, storage_error& ec); + + status_t initialize(settings_interface const&, storage_error& ec); + + private: + + file_pointer open_file(file_index_t idx, open_mode_t mode, std::int64_t offset + , storage_error& ec); + + void need_partfile(); + bool use_partfile(file_index_t index) const; + void use_partfile(file_index_t index, bool b); + + file_storage const& m_files; + std::unique_ptr m_mapped_files; + std::string m_save_path; + stat_cache m_stat_cache; + + aux::vector m_file_priority; + + // 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; + + std::string m_part_file_name; + std::unique_ptr m_part_file; + }; +} +} +#endif + diff --git a/include/libtorrent/aux_/proxy_settings.hpp b/include/libtorrent/aux_/proxy_settings.hpp new file mode 100644 index 0000000..57cf0b6 --- /dev/null +++ b/include/libtorrent/aux_/proxy_settings.hpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +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_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..44815ed --- /dev/null +++ b/include/libtorrent/aux_/range.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2017, 2019-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_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_/receive_buffer.hpp b/include/libtorrent/aux_/receive_buffer.hpp new file mode 100644 index 0000000..c47777d --- /dev/null +++ b/include/libtorrent/aux_/receive_buffer.hpp @@ -0,0 +1,230 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2018, 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/aux_/buffer.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +#include + +namespace libtorrent { +namespace aux { + +struct TORRENT_EXTRA_EXPORT receive_buffer +{ + friend struct crypto_receive_buffer; + + // explicitly disallow assignment, to silence msvc warning + receive_buffer& operator=(receive_buffer const&) = delete; + + 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: + + // 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) + {} + + // explicitly disallow assignment, to silence msvc warning + crypto_receive_buffer& operator=(crypto_receive_buffer const&) = delete; + + 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: + + int m_recv_pos = (std::numeric_limits::max)(); + int m_packet_size = 0; + receive_buffer& m_connection_buffer; +}; +#endif // TORRENT_DISABLE_ENCRYPTION + +} // namespace aux +} // namespace libtorrent + +#endif // #ifndef TORRENT_RECEIVE_BUFFER_HPP_INCLUDED diff --git a/include/libtorrent/aux_/resolver.hpp b/include/libtorrent/aux_/resolver.hpp new file mode 100644 index 0000000..ab9a7e6 --- /dev/null +++ b/include/libtorrent/aux_/resolver.hpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2010, 2014-2017, 2019-2020, Arvid Norberg +Copyright (c) 2017, 2020, 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_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_context.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/resolver_interface.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent { +namespace aux { + +struct TORRENT_EXTRA_EXPORT resolver final : resolver_interface +{ + explicit resolver(io_context& 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::results_type ips + , 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_context& 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/aux_/resolver_interface.hpp b/include/libtorrent/aux_/resolver_interface.hpp new file mode 100644 index 0000000..891d831 --- /dev/null +++ b/include/libtorrent/aux_/resolver_interface.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2010, 2014-2018, 2020, Arvid Norberg +Copyright (c) 2017, 2020, 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_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 { +namespace aux { + +// 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/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..28b8b78 --- /dev/null +++ b/include/libtorrent/aux_/scope_end.hpp @@ -0,0 +1,64 @@ +/* + +Copyright (c) 2016-2017, 2019-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_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..10007d8 --- /dev/null +++ b/include/libtorrent/aux_/session_call.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2014, Arvid Norberg, Steven Siloti +Copyright (c) 2014-2016, 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_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..7708596 --- /dev/null +++ b/include/libtorrent/aux_/session_impl.hpp @@ -0,0 +1,1392 @@ +/* + +Copyright (c) 2006-2020, Arvid Norberg +Copyright (c) 2014-2019, Steven Siloti +Copyright (c) 2015, Thomas +Copyright (c) 2015-2020, Alden Torres +Copyright (c) 2016-2017, Pavel Pimenov +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/aux_/torrent_list.hpp" +#include "libtorrent/session_params.hpp" // for disk_io_constructor_type + +#ifdef TORRENT_SSL_PEERS +#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/aux_/bandwidth_manager.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/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/aux_/utp_socket_manager.hpp" +#include "libtorrent/bloom_filter.hpp" +#include "libtorrent/peer_class.hpp" +#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/aux_/resolver.hpp" +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/aux_/portmap.hpp" +#include "libtorrent/aux_/lsd.hpp" +#include "libtorrent/io_context.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 { + +TORRENT_VERSION_NAMESPACE_3 + struct plugin; +TORRENT_VERSION_NAMESPACE_3_END + + struct upnp; + struct natpmp; + struct lsd; + struct torrent; + struct 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 + + struct unique_ptr_less + { + using is_transparent = std::true_type; + template + bool operator()(std::unique_ptr const& lhs, std::unique_ptr const& rhs) const + { return lhs < rhs; } + template + bool operator()(std::unique_ptr const& lhs, T* rhs) const + { return lhs.get() < rhs; } + template + bool operator()(T* lhs, std::unique_ptr const& rhs) const + { return lhs < rhs.get(); } + }; + + 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); + + void apply_deprecated_dht_settings(settings_pack& sett, bdecode_node const& s); + + 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 + , 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 struct libtorrent::invariant_access; +#endif + using connection_map = std::set>; + + session_impl(io_context&, settings_pack const&, disk_io_constructor_type, session_flags_t); + ~session_impl() override; + + session_impl(session_impl const&) = delete; + session_impl& operator=(session_impl const&) = delete; + + void start_session(); + + void init_peer_class_filter(bool unlimited_local); + + void call_abort() + { + auto ptr = shared_from_this(); + dispatch(m_io_context, make_handler([ptr] { ptr->abort(); } + , m_abort_handler_storage, *this)); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + using ext_function_t + = std::function(torrent_handle const&, client_data_t)>; + + 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, client_data_t const user) override + { return m_f(t, user); } + ext_function_t m_f; + }; + + void add_extension(std::function( + torrent_handle const&, client_data_t)> 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_context& get_context() override { return m_io_context; } + 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&, transport); + void on_accept_connection(true_tcp_socket s, error_code const& + , std::weak_ptr, transport); + + void incoming_connection(socket_type); + + std::weak_ptr find_torrent(info_hash_t const&) 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; } +#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(info_hash_t const& ih, std::shared_ptr const& t) override; + + // when downloading metadata for a torrent, we may learn that it is a + // hybrid (supporting v1 and v2), which means it has two info-hashes. + // This function updates the index for the torrent, so we can find it + // using both the v1 and v2 info-hashes. + void update_torrent_info_hash(std::shared_ptr const& t + , info_hash_t const& old_ih) override; + + std::shared_ptr delay_load_torrent(info_hash_t const& info_hash + , peer_connection* pc) override; + void set_queue_position(torrent*, queue_position_t) 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); +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + void set_dht_settings(dht::dht_settings const& s); + dht::dht_settings get_dht_settings() const; +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + + // you must give up ownership of the dht state + void set_dht_state(dht::dht_state&& state); + + 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, client_data_t userdata); + +#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; +#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, listen_socket_handle const&) 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 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, socket_type_t 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, client_data_t 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::tuple, info_hash_t, bool> + add_torrent_impl(add_torrent_params&& p, error_code& ec); + std::tuple, info_hash_t, bool> + add_torrent_impl(add_torrent_params const& p, error_code& ec) = delete; + void async_add_torrent(add_torrent_params* params); + + 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 + + aux::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 + + 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; + +#if TORRENT_ABI_VERSION <= 2 + void save_state(entry* e, save_state_flags_t flags) const; + void load_state(bdecode_node const* e, save_state_flags_t flags); +#endif + session_params session_state(save_state_flags_t flags) const; + + 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(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 + , listen_socket_handle const&) 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::aux::utp_socket_manager* utp_socket_manager() override + { return &m_utp_socket_manager; } +#ifdef TORRENT_SSL_PEERS + libtorrent::aux::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_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_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(std::shared_ptr const& s); + void start_upnp(std::shared_ptr const& 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_context& m_io_context; + +#if TORRENT_USE_SSL + // this is a generic SSL context used when talking to HTTPS servers + ssl::context m_ssl_ctx; +#endif + +#ifdef TORRENT_SSL_PEERS + // 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. + std::unique_ptr 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) + aux::torrent_list m_torrents; + + // all torrents that are downloading or queued, + // ordered by their queue position + aux::vector m_download_queue; + + // 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_context alive until we have posted the job + // to clear the undead peers + executor_work_guard m_work; + + // this maps sockets to their peer_connection + // object. It is the complete list of all connected + // peers. + connection_map m_connections; + +#ifdef TORRENT_SSL_PEERS + // 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, unique_ptr_less> m_incoming_sockets; +#endif + + // 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; + boost::optional m_i2p_listen_socket; +#endif + +#if TORRENT_USE_SSL + ssl::context* ssl_ctx() override { return &m_ssl_ctx; } +#endif +#ifdef TORRENT_SSL_PEERS + void on_incoming_utp_ssl(socket_type s); + void ssl_handshake(error_code const& ec, socket_type* 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::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::aux::utp_socket_manager m_utp_socket_manager; + +#ifdef TORRENT_SSL_PEERS + // used for uTP connections over SSL + libtorrent::aux::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; + + // 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 + aux::handler_storage m_abort_handler_storage; + + // submit_deferred may not fail + aux::handler_storage m_submit_jobs_handler_storage; + + // 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) + std::size_t 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) + std::size_t 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 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& tracker_ips + , struct tracker_response const& resp) override; + void tracker_request_error(tracker_request const& r + , error_code const& ec, operation_t op, 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..f456546 --- /dev/null +++ b/include/libtorrent/aux_/session_interface.hpp @@ -0,0 +1,316 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016-2018, Steven Siloti +Copyright (c) 2016-2017, 2019-2020, Alden Torres +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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_context.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 "libtorrent/info_hash.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/ssl.hpp" + +#include +#include + +namespace libtorrent { + + struct peer_connection; + struct torrent; + struct peer_class_set; + struct peer_class_pool; + struct disk_observer; + struct torrent_peer; + struct disk_interface; + struct tracker_request; + struct request_callback; + struct external_ip; + struct torrent_peer_allocator_interface; + struct counters; + +namespace aux { + struct utp_socket_manager; + struct bandwidth_channel; + struct bandwidth_manager; + struct resolver_interface; + struct alert_manager; +} + + // 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; + + 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_context& get_context() = 0; + virtual aux::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(info_hash_t const& info_hash) const = 0; + virtual std::weak_ptr find_disconnect_candidate_torrent() const = 0; + virtual std::shared_ptr delay_load_torrent(info_hash_t const& info_hash + , peer_connection* pc) = 0; + virtual void insert_torrent(info_hash_t const& ih, std::shared_ptr const& t) = 0; + virtual void update_torrent_info_hash(std::shared_ptr const& t + , info_hash_t const& old_ih) = 0; + 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; + + virtual void queue_tracker_request(tracker_request req + , std::weak_ptr c) = 0; + + // peer-classes + virtual void set_peer_classes(peer_class_set* s, address const& a, socket_type_t 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::aux::utp_socket_manager* utp_socket_manager() = 0; + virtual void inc_boost_connections() = 0; + virtual std::vector& block_info_storage() = 0; + +#ifdef TORRENT_SSL_PEERS + virtual libtorrent::aux::utp_socket_manager* ssl_utp_socket_manager() = 0; +#endif +#if TORRENT_USE_SSL + virtual 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; +#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..261968f --- /dev/null +++ b/include/libtorrent/aux_/session_settings.hpp @@ -0,0 +1,190 @@ +/* + +Copyright (c) 2014, 2016-2017, 2019-2020, Arvid Norberg +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_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 final : settings_interface + { + void set_str(int name, std::string value) override + { + std::unique_lock l(m_mutex); + return m_store.set_str(name, std::move(value)); + } + void set_int(int name, int value) override + { + std::unique_lock l(m_mutex); + m_store.set_int(name, value); + } + void set_bool(int name, bool value) override + { + std::unique_lock l(m_mutex); + m_store.set_bool(name, value); + } + + std::string const& get_str(int name) const override + { + std::unique_lock l(m_mutex); + return m_store.get_str(name); + } + int get_int(int name) const override + { + std::unique_lock l(m_mutex); + return m_store.get_int(name); + } + bool get_bool(int name) const override + { + std::unique_lock l(m_mutex); + return m_store.get_bool(name); + } + + bool has_val(int) const override { return true; } + + session_settings(); + explicit session_settings(settings_pack const&); + + void bulk_set(std::function); + void bulk_get(std::function) const; + + // since std::mutex is not copyable, we have to explicitly just copy the + // underlying storage object. Lock the object we're copying from first, + // and forward to a private copy constructor to keep the lock alive + // inspired by https://www.justsoftwaresolutions.co.uk/threading/thread-safe-copy-constructors.html + session_settings(session_settings const& lhs) + : session_settings(lhs, std::unique_lock(lhs.m_mutex)) + {} + session_settings(session_settings&& lhs) + : session_settings(std::move(lhs), std::unique_lock(lhs.m_mutex)) + {} + + session_settings& operator=(session_settings const& rhs) + { + if (this == &rhs) return *this; + // in C++17, use a single std::scoped_lock instead + std::lock(rhs.m_mutex, m_mutex); + std::unique_lock l1(rhs.m_mutex, std::adopt_lock); + std::unique_lock l2(m_mutex, std::adopt_lock); + m_store = rhs.m_store; + return *this; + } + session_settings& operator=(session_settings&& rhs) + { + if (this == &rhs) return *this; + m_store = std::move(rhs.m_store); + return *this; + } + + private: + + session_settings(session_settings const& lhs, std::unique_lock const&) + : settings_interface(lhs) + , m_store(lhs.m_store) + {} + + session_settings(session_settings&& lhs, std::unique_lock const&) + : settings_interface(lhs) + , m_store(std::move(lhs.m_store)) + {} + + 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..97cc677 --- /dev/null +++ b/include/libtorrent/aux_/session_udp_sockets.hpp @@ -0,0 +1,78 @@ +/* + +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017, 2019-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_SESSION_UDP_SOCKETS_HPP_INCLUDED +#define TORRENT_SESSION_UDP_SOCKETS_HPP_INCLUDED + +#include "libtorrent/aux_/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 { + + struct 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_context& 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..69f049c --- /dev/null +++ b/include/libtorrent/aux_/set_socket_buffer.hpp @@ -0,0 +1,92 @@ +/* + +Copyright (c) 2018, Arvid Norberg, Magnus Jonsson +Copyright (c) 2018, 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_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_/set_traffic_class.hpp b/include/libtorrent/aux_/set_traffic_class.hpp new file mode 100644 index 0000000..068e7a9 --- /dev/null +++ b/include/libtorrent/aux_/set_traffic_class.hpp @@ -0,0 +1,60 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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_TRAFFIC_CLASS_HPP +#define TORRENT_SET_TRAFFIC_CLASS_HPP + +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket.hpp" + +namespace libtorrent { +namespace aux { + + template + void set_traffic_class(Socket& s, int v, error_code& ec) + { +#ifdef IP_DSCP_TRAFFIC_TYPE + s.set_option(dscp_traffic_type((v & 0xff) >> 2), ec); + if (!ec) return; + ec.clear(); +#endif +#if defined IPV6_TCLASS + if (is_v6(s.local_endpoint(ec))) + s.set_option(traffic_class(v & 0xfc), ec); + else if (!ec) +#endif + s.set_option(type_of_service(v & 0xfc), ec); + } + +}} + +#endif diff --git a/include/libtorrent/aux_/sha512.hpp b/include/libtorrent/aux_/sha512.hpp new file mode 100644 index 0000000..4e8ceaf --- /dev/null +++ b/include/libtorrent/aux_/sha512.hpp @@ -0,0 +1,33 @@ +#ifndef TORRENT_SHA512_HPP_INCLUDED +#define TORRENT_SHA512_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if !defined TORRENT_USE_LIBGCRYPT \ + && !TORRENT_USE_COMMONCRYPTO \ + && !TORRENT_USE_CNG \ + && !TORRENT_USE_CRYPTOAPI_SHA_512 \ + && !defined TORRENT_USE_LIBCRYPTO + +#include + +namespace libtorrent { +namespace aux { + + 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* md); + TORRENT_EXTRA_EXPORT int SHA512_update(sha512_ctx* md + , std::uint8_t const* data, std::size_t len); + TORRENT_EXTRA_EXPORT int SHA512_final(std::uint8_t* digest, sha512_ctx* md); +} +} + +#endif +#endif diff --git a/include/libtorrent/aux_/socket_type.hpp b/include/libtorrent/aux_/socket_type.hpp new file mode 100644 index 0000000..3b40919 --- /dev/null +++ b/include/libtorrent/aux_/socket_type.hpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2007, 2009-2010, 2012-2013, 2017-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/socket.hpp" +#include "libtorrent/socks5_stream.hpp" +#include "libtorrent/http_stream.hpp" +#include "libtorrent/i2p_stream.hpp" +#include "libtorrent/aux_/utp_stream.hpp" +#include "libtorrent/aux_/polymorphic_socket.hpp" +#include "libtorrent/socket_type.hpp" + +#if TORRENT_USE_SSL +#include "libtorrent/ssl_stream.hpp" +#endif + +#include "libtorrent/debug.hpp" + +namespace libtorrent { +namespace aux { + + using socket_type = polymorphic_socket< + tcp::socket + , socks5_stream + , http_stream + , utp_stream +#if TORRENT_USE_I2P + , i2p_stream +#endif +#if TORRENT_USE_SSL + , ssl_stream + , ssl_stream + , ssl_stream + , ssl_stream +#endif + >; + + // 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); + + socket_type_t socket_type_idx(socket_type const& s); + + char const* socket_type_name(socket_type const& s); + + // this is only relevant for uTP connections + void set_close_reason(socket_type& s, close_reason_t code); + close_reason_t get_close_reason(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_free_list.hpp b/include/libtorrent/aux_/storage_free_list.hpp new file mode 100644 index 0000000..2a7ac36 --- /dev/null +++ b/include/libtorrent/aux_/storage_free_list.hpp @@ -0,0 +1,73 @@ +/* + +Copyright (c) 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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_FREE_LIST_HPP_INCLUDE +#define TORRENT_STORAGE_FREE_LIST_HPP_INCLUDE + +#include +#include "libtorrent/storage_defs.hpp" + +namespace libtorrent { +namespace aux { + + struct storage_free_list + { + // if we don't already have any free slots, use next + storage_index_t new_index(storage_index_t const next) + { + // make sure we can remove this torrent without causing a memory + // allocation, by triggering the allocation now instead + m_free_slots.reserve(static_cast(next) + 1); + return m_free_slots.empty() ? next : pop(); + } + + void add(storage_index_t const i) { m_free_slots.push_back(i); } + + std::size_t size() const { return m_free_slots.size(); } + + private: + + storage_index_t pop() + { + TORRENT_ASSERT(!m_free_slots.empty()); + storage_index_t const ret = m_free_slots.back(); + m_free_slots.pop_back(); + return ret; + } + + private: + std::vector m_free_slots; + }; +} +} +#endif + diff --git a/include/libtorrent/aux_/storage_utils.hpp b/include/libtorrent/aux_/storage_utils.hpp new file mode 100644 index 0000000..7e606b2 --- /dev/null +++ b/include/libtorrent/aux_/storage_utils.hpp @@ -0,0 +1,127 @@ +/* + +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2018, 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_STORAGE_UTILS_HPP_INCLUDE +#define TORRENT_STORAGE_UTILS_HPP_INCLUDE + +#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" +#include "libtorrent/error_code.hpp" + +namespace libtorrent { + + 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 save_path + , std::string const& destination_save_path + , std::function const& move_partfile + , 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& cache + , storage_error& ec); + + TORRENT_EXTRA_EXPORT int read_zeroes(span bufs); + + TORRENT_EXTRA_EXPORT void initialize_storage( + file_storage const& fs + , std::string const& save_path + , stat_cache& sc + , aux::vector const& file_priority + , std::function create_file + , std::function create_link + , std::function oversized_file + , storage_error& ec); + + TORRENT_EXTRA_EXPORT void create_symlink( + std::string const& target + , std::string const& link + , storage_error& ec); +}} + +#endif diff --git a/include/libtorrent/aux_/store_buffer.hpp b/include/libtorrent/aux_/store_buffer.hpp new file mode 100644 index 0000000..10cd269 --- /dev/null +++ b/include/libtorrent/aux_/store_buffer.hpp @@ -0,0 +1,150 @@ +/* + +Copyright (c) 2016, 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_STORE_BUFFER +#define TORRENT_STORE_BUFFER + +#include +#include + +#include "libtorrent/storage_defs.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + +namespace libtorrent { +namespace aux { + +// uniquely identifies a torrent and offset. It is used as the key in the +// dictionary mapping locations to write jobs +struct torrent_location +{ + torrent_location(storage_index_t const t, piece_index_t const p, int o) + : torrent(t), piece(p), offset(o) {} + storage_index_t torrent; + piece_index_t piece; + int offset; + bool operator==(torrent_location const& rhs) const + { + return std::tie(torrent, piece, offset) + == std::tie(rhs.torrent, rhs.piece, rhs.offset); + } +}; + +} +} + +namespace std { + +template <> +struct hash +{ + using argument_type = libtorrent::aux::torrent_location; + using result_type = std::size_t; + std::size_t operator()(argument_type const& l) const + { + using namespace libtorrent; + std::size_t ret = 0; + boost::hash_combine(ret, std::hash{}(l.torrent)); + boost::hash_combine(ret, std::hash{}(l.piece)); + boost::hash_combine(ret, std::hash{}(l.offset)); + return ret; + } +}; + +} + +namespace libtorrent { +namespace aux { + +struct store_buffer +{ + template + bool get(torrent_location const loc, Fun f) const + { + std::unique_lock l(m_mutex); + auto const it = m_store_buffer.find(loc); + if (it != m_store_buffer.end()) + { + f(it->second); + return true; + } + return false; + } + + template + int get2(torrent_location const loc1, torrent_location const loc2, Fun f) const + { + std::unique_lock l(m_mutex); + auto const it1 = m_store_buffer.find(loc1); + auto const it2 = m_store_buffer.find(loc2); + char const* buf1 = (it1 == m_store_buffer.end()) ? nullptr : it1->second; + char const* buf2 = (it2 == m_store_buffer.end()) ? nullptr : it2->second; + + if (buf1 == nullptr && buf2 == nullptr) + return 0; + + return f(buf1, buf2); + } + + void insert(torrent_location const loc, char const* buf) + { + std::lock_guard l(m_mutex); + m_store_buffer.insert({loc, buf}); + } + + void erase(torrent_location const loc) + { + std::lock_guard l(m_mutex); + auto it = m_store_buffer.find(loc); + TORRENT_ASSERT(it != m_store_buffer.end()); + m_store_buffer.erase(it); + } + + std::size_t size() const + { + return m_store_buffer.size(); + } + +private: + + mutable std::mutex m_mutex; + std::unordered_map m_store_buffer; +}; + +} +} + +#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_/strview_less.hpp b/include/libtorrent/aux_/strview_less.hpp new file mode 100644 index 0000000..57d9e75 --- /dev/null +++ b/include/libtorrent/aux_/strview_less.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2016, 2019-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_STRLESS_HPP_INCLUDED +#define TORRENT_STRLESS_HPP_INCLUDED + +#include + +namespace libtorrent { +namespace aux { + // this enables us to compare a string_view against the std::string that's + // held by the std::map + struct strview_less + { + using is_transparent = std::true_type; + template + bool operator()(T1 const& rhs, T2 const& lhs) const + { return rhs < lhs; } + }; +} +} + +#endif diff --git a/include/libtorrent/aux_/suggest_piece.hpp b/include/libtorrent/aux_/suggest_piece.hpp new file mode 100644 index 0000000..36f64d0 --- /dev/null +++ b/include/libtorrent/aux_/suggest_piece.hpp @@ -0,0 +1,128 @@ +/* + +Copyright (c) 2016, 2018, 2020, Arvid Norberg +Copyright (c) 2016-2017, 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_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..5d47131 --- /dev/null +++ b/include/libtorrent/aux_/throw.hpp @@ -0,0 +1,54 @@ +/* + +Copyright (c) 2017, 2019-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_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..e3c14f4 --- /dev/null +++ b/include/libtorrent/aux_/time.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2015, 2017, 2020, Arvid Norberg +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_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_/timestamp_history.hpp b/include/libtorrent/aux_/timestamp_history.hpp new file mode 100644 index 0000000..d6d6722 --- /dev/null +++ b/include/libtorrent/aux_/timestamp_history.hpp @@ -0,0 +1,88 @@ +/* + +Copyright (c) 2010-2012, 2014-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 TIMESTAMP_HISTORY_HPP +#define TIMESTAMP_HISTORY_HPP + +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { +namespace aux { + +// 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/aux_/torrent_impl.hpp b/include/libtorrent/aux_/torrent_impl.hpp new file mode 100644 index 0000000..7f16bc7 --- /dev/null +++ b/include/libtorrent/aux_/torrent_impl.hpp @@ -0,0 +1,80 @@ +/* + +Copyright (c) 2017, 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_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_/torrent_list.hpp b/include/libtorrent/aux_/torrent_list.hpp new file mode 100644 index 0000000..99dd0f3 --- /dev/null +++ b/include/libtorrent/aux_/torrent_list.hpp @@ -0,0 +1,260 @@ +/* + +Copyright (c) 2018, Steven Siloti +Copyright (c) 2019-2020, Arvid Norberg +Copyright (c) 2019, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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_LIST_HPP_INCLUDED +#define TORRENT_TORRENT_LIST_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/info_hash.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/aux_/array.hpp" +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/aux_/scope_end.hpp" + +#include // for shared_ptr +#include +#include + +#if TORRENT_USE_INVARIANT_CHECKS +#include +#endif + +namespace libtorrent { +namespace aux { + +template +struct torrent_list +{ + // These are non-owning pointers. Lifetime is managed by the `torrent_array` + using torrent_map = std::unordered_map; + using torrent_array = std::vector>; + + using iterator = typename torrent_array::iterator; + using const_iterator = typename torrent_array::const_iterator; + + using value_type = typename torrent_array::value_type; + + bool empty() const { return m_array.empty(); } + + iterator begin() { return m_array.begin(); } + iterator end() { return m_array.end(); } + + const_iterator begin() const { return m_array.begin(); } + const_iterator end() const { return m_array.end(); } + + std::size_t size() const { return m_array.size(); } + + T* operator[](std::size_t const idx) + { + TORRENT_ASSERT(idx < m_array.size()); + return m_array[idx].get(); + } + + T const* operator[](std::size_t const idx) const + { + TORRENT_ASSERT(idx < m_array.size()); + return m_array[idx].get(); + } + + bool insert(info_hash_t const& ih, std::shared_ptr t) + { + TORRENT_ASSERT(t); + + bool duplicate = false; + ih.for_each([&](sha1_hash const& hash, protocol_version) + { + if (m_index.find(hash) != m_index.end()) duplicate = true; + }); + + // if we already have a torrent with this hash, don't do anything + if (duplicate) return false; + + aux::array rollback({ false, false, false, false}); + auto abort_add = aux::scope_end([&] + { + ih.for_each([&](sha1_hash const& hash, protocol_version const v) + { + if (rollback[int(v)]) + m_index.erase(hash); + +#if !defined TORRENT_DISABLE_ENCRYPTION + if (rollback[2 + int(v)]) + { + static char const req2[4] = { 'r', 'e', 'q', '2' }; + hasher h(req2); + h.update(hash); + m_obfuscated_index.erase(h.final()); + } +#endif + }); + }); + + ih.for_each([&](sha1_hash const& hash, protocol_version const v) + { + if (m_index.insert({hash, t.get()}).second) + rollback[int(v)] = true; + +#if !defined TORRENT_DISABLE_ENCRYPTION + static char const req2[4] = { 'r', 'e', 'q', '2' }; + hasher h(req2); + h.update(hash); + // this is SHA1("req2" + info-hash), used for + // encrypted hand shakes + if (m_obfuscated_index.insert({h.final(), t.get()}).second) + rollback[2 + int(v)] = true; +#endif + }); + + m_array.emplace_back(std::move(t)); + + abort_add.disarm(); + + return true; + } + +#if !defined TORRENT_DISABLE_ENCRYPTION + T* find_obfuscated(sha1_hash const& ih) + { + auto const i = m_obfuscated_index.find(ih); + if (i == m_obfuscated_index.end()) return nullptr; + return i->second; + } +#endif + + T* find(sha1_hash const& ih) const + { + auto const i = m_index.find(ih); + if (i == m_index.end()) return nullptr; + return i->second; + } + + bool erase(info_hash_t const& ih) + { + INVARIANT_CHECK; + + T* found = nullptr; + ih.for_each([&](sha1_hash const& hash, protocol_version) + { + auto const i = m_index.find(hash); + if (i != m_index.end()) + { + TORRENT_ASSERT(found == nullptr || found == i->second); + found = i->second; + m_index.erase(i); + } + +#if !defined TORRENT_DISABLE_ENCRYPTION + static char const req2[4] = { 'r', 'e', 'q', '2' }; + hasher h(req2); + h.update(hash); + m_obfuscated_index.erase(h.final()); +#endif + }); + if (!found) return false; + + auto const array_iter = std::find_if(m_array.begin(), m_array.end() + , [&](std::shared_ptr const& p) { return p.get() == found; }); + TORRENT_ASSERT(array_iter != m_array.end()); + + TORRENT_ASSERT(m_index.find(ih.v1) == m_index.end()); + + if (array_iter != m_array.end() - 1) + std::swap(*array_iter, m_array.back()); + + // This is where we, potentially, remove the last reference + m_array.pop_back(); + + return true; + } + + void clear() + { + INVARIANT_CHECK; + + m_array.clear(); + m_index.clear(); +#if !defined TORRENT_DISABLE_ENCRYPTION + m_obfuscated_index.clear(); +#endif + } + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const + { + std::set all_torrents; + std::set all_indexed_torrents; +#if !defined TORRENT_DISABLE_ENCRYPTION + std::set all_obf_indexed_torrents; +#endif + + for (auto const& t : m_array) + all_torrents.insert(t.get()); + + for (auto const& t : m_index) + { + all_indexed_torrents.insert(t.second); + } +#if !defined TORRENT_DISABLE_ENCRYPTION + for (auto const& t : m_obfuscated_index) + { + all_obf_indexed_torrents.insert(t.second); + } +#endif + + TORRENT_ASSERT(all_torrents == all_indexed_torrents); +#if !defined TORRENT_DISABLE_ENCRYPTION + TORRENT_ASSERT(all_torrents == all_obf_indexed_torrents); +#endif + } +#endif + +private: + + torrent_array m_array; + + torrent_map m_index; + +#if !defined TORRENT_DISABLE_ENCRYPTION + // this maps obfuscated hashes to torrents. It's only + // used when encryption is enabled + torrent_map m_obfuscated_index; +#endif +}; + +} +} + +#endif + diff --git a/include/libtorrent/aux_/unique_ptr.hpp b/include/libtorrent/aux_/unique_ptr.hpp new file mode 100644 index 0000000..d7e6cca --- /dev/null +++ b/include/libtorrent/aux_/unique_ptr.hpp @@ -0,0 +1,66 @@ +/* + +Copyright (c) 2017, Alden Torres +Copyright (c) 2018-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_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) {} + + decltype(auto) operator[](IndexType idx) const + { + TORRENT_ASSERT(idx >= IndexType(0)); + return this->base::operator[](std::size_t(static_cast(idx))); + } + }; + +}} + +#endif diff --git a/include/libtorrent/aux_/utp_socket_manager.hpp b/include/libtorrent/aux_/utp_socket_manager.hpp new file mode 100644 index 0000000..2a8211f --- /dev/null +++ b/include/libtorrent/aux_/utp_socket_manager.hpp @@ -0,0 +1,202 @@ +/* + +Copyright (c) 2010, 2012-2020, Arvid Norberg +Copyright (c) 2016-2017, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/aux_/packet_pool.hpp" + +namespace libtorrent { + +struct counters; + +namespace aux { + + struct utp_stream; + struct utp_socket_impl; + + // 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; + + utp_socket_manager(send_fun_t send_fun + , incoming_utp_callback_t cb + , io_context& 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); } + + int mtu_for_dest(address const& addr) const; + 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); + + aux::packet_ptr acquire_packet(int const allocate) { return m_packet_pool.acquire(allocate); } + void release_packet(aux::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_context& 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; + + aux::packet_pool m_packet_pool; + }; +} +} + +#endif diff --git a/include/libtorrent/aux_/utp_stream.hpp b/include/libtorrent/aux_/utp_stream.hpp new file mode 100644 index 0000000..81bce4d --- /dev/null +++ b/include/libtorrent/aux_/utp_stream.hpp @@ -0,0 +1,974 @@ +/* + +Copyright (c) 2010-2020, Arvid Norberg +Copyright (c) 2015-2018, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/udp_socket.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/aux_/packet_buffer.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/close_reason.hpp" +#include "libtorrent/aux_/timestamp_history.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t + +#include + +#ifndef BOOST_NO_EXCEPTIONS +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +namespace libtorrent { +namespace aux { + +#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; + aux::write_impl(v, p); + return *this; + } + operator T() const + { + const char* p = m_storage; + return aux::read_impl(p, aux::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 : std::uint8_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 : std::uint8_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; +struct utp_stream; + +// 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; + + using executor_type = tcp::socket::executor_type; + executor_type get_executor() { return m_io_service.get_executor(); } + + explicit utp_stream(io_context& io_context); + ~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; + + lowest_layer_type& lowest_layer() { return *this; } + lowest_layer_type const& lowest_layer() const { return *this; } + + // used for incoming connections + void set_impl(utp_socket_impl*); + 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 + + void non_blocking(bool, 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 + void set_option(SettableSocketOption const&, error_code&) { } + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption&) {} +#endif + + template + void get_option(GettableSocketOption&, error_code&) {} + + void cancel(error_code&) + { + cancel_handlers(boost::asio::error::operation_aborted); + } + + void close(); + void close(error_code const&) { close(); } + + void set_close_reason(close_reason_t code); + close_reason_t get_close_reason() const; + + bool is_open() const { return m_open; } + + int read_buffer_size() const; + static void on_read(utp_stream* self, std::size_t bytes_transferred + , error_code const& ec, bool shutdown); + static void on_write(utp_stream* self, std::size_t bytes_transferred + , error_code const& ec, bool shutdown); + static void on_connect(utp_stream* self, error_code const& ec, bool shutdown); + static void on_close_reason(utp_stream* self, close_reason_t reason); + + void add_read_buffer(void* buf, int len); + void issue_read(); + void add_write_buffer(void const* buf, int len); + void issue_write(); + std::size_t read_some(bool clear_buffers); + std::size_t write_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(); } + + template + void async_connect(endpoint_type const& endpoint, Handler handler) + { + if (m_impl == nullptr) + { + post(m_io_service, std::bind(std::move(handler), boost::asio::error::not_connected)); + return; + } + + m_connect_handler = std::move(handler); + do_connect(endpoint); + } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler handler) + { + if (m_impl == nullptr) + { + post(m_io_service, std::bind(std::move(handler), boost::asio::error::not_connected, std::size_t(0))); + return; + } + + TORRENT_ASSERT(!m_read_handler); + if (m_read_handler) + { + post(m_io_service, std::bind(std::move(handler), boost::asio::error::operation_not_supported, std::size_t(0))); + return; + } + std::size_t bytes_added = 0; + for (auto i = buffer_sequence_begin(buffers) + , end(buffer_sequence_end(buffers)); i != end; ++i) + { + if (i->size() == 0) continue; + add_read_buffer(i->data(), int(i->size())); + bytes_added += i->size(); + } + if (bytes_added == 0) + { + // if we're reading 0 bytes, post handler immediately + // asio's SSL layer depends on this behavior + post(m_io_service, std::bind(std::move(handler), error_code(), std::size_t(0))); + return; + } + + m_read_handler = std::move(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 + + for (auto i = buffer_sequence_begin(buffers) + , end(buffer_sequence_end(buffers)); i != end; ++i) + { + add_read_buffer(i->data(), int(i->size())); +#if TORRENT_USE_ASSERTS + buf_size += i->size(); +#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(!m_write_handler); + if (m_impl == nullptr) + { + ec = boost::asio::error::not_connected; + return 0; + } + +#if TORRENT_USE_ASSERTS + size_t buf_size = 0; +#endif + + for (auto i = buffer_sequence_begin(buffers) + , end(buffer_sequence_end(buffers)); i != end; ++i) + { + add_write_buffer(i->data(), int(i->size())); +#if TORRENT_USE_ASSERTS + buf_size += i->size(); +#endif + } + std::size_t ret = write_some(true); + TORRENT_ASSERT(ret <= buf_size); + if(ret == 0) + { + ec = boost::asio::error::would_block; + return 0; + } + return ret; + } + +#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 handler) + { + if (m_impl == nullptr) + { + post(m_io_service, std::bind(std::move(handler) + , boost::asio::error::not_connected, std::size_t(0))); + return; + } + + TORRENT_ASSERT(!m_write_handler); + if (m_write_handler) + { + post(m_io_service, std::bind(std::move(handler) + , boost::asio::error::operation_not_supported, std::size_t(0))); + return; + } + + std::size_t bytes_added = 0; + for (auto i = buffer_sequence_begin(buffers) + , end(buffer_sequence_end(buffers)); i != end; ++i) + { + if (i->size() == 0) continue; + add_write_buffer(i->data(), int(i->size())); + bytes_added += i->size(); + } + if (bytes_added == 0) + { + // if we're writing 0 bytes, post handler immediately + // asio's SSL layer depends on this behavior + post(m_io_service, std::bind(std::move(handler), error_code(), std::size_t(0))); + return; + } + m_write_handler = std::move(handler); + issue_write(); + } + +#if BOOST_VERSION >= 106600 + // Compatiblity with the async_wait method introduced in boost 1.66 + + enum wait_type { wait_read, wait_write, wait_error }; + + template + void async_wait(wait_type type, Handler handler) { + switch(type) + { + case wait_read: + async_read_some(boost::asio::null_buffers() + , [handler](error_code ec, size_t) { handler(std::move(ec)); }); + break; + + case wait_write: + async_write_some(boost::asio::null_buffers() + , [handler](error_code ec, size_t) { handler(std::move(ec)); }); + break; + + case wait_error: + post(m_io_service, std::bind(std::move(handler) + , boost::asio::error::operation_not_supported)); + break; + } + } +#endif + +private: + + void cancel_handlers(error_code const&); + + std::function m_connect_handler; + std::function m_read_handler; + std::function m_write_handler; + + io_context& 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; +}; + +// 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 +{ +#if TORRENT_USE_INVARIANT_CHECKS + friend struct ::libtorrent::invariant_access; +#endif + + utp_socket_impl(std::uint16_t recv_id, std::uint16_t send_id + , utp_stream* userdata, utp_socket_manager& sm); + + ~utp_socket_impl(); + + void tick(time_point now); + void init_mtu(int 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; + 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 send_ack(); + void socket_drained(); + + void set_userdata(utp_stream* s) { m_userdata = s; } + void abort(); + udp::endpoint remote_endpoint() const; + + std::uint16_t receive_id() const { return m_recv_id; } + bool match(udp::endpoint const& ep, std::uint16_t id) const; + + // non-copyable + utp_socket_impl(utp_socket_impl const&) = delete; + utp_socket_impl const& operator=(utp_socket_impl const&) = delete; + + // The underlying UDP socket this uTP socket is bound to + // TODO: it would be nice to make this private + std::weak_ptr m_sock; + + void add_write_buffer(void const* buf, int len); + void add_read_buffer(void* buf, int len); + + int send_delay() const { return m_send_delay; } + int recv_delay() const { return m_recv_delay; } + + void issue_read(); + void issue_write(); + + void do_connect(tcp::endpoint const& ep); + + std::size_t read_some(bool const clear_buffers); + std::size_t write_some(bool const clear_buffers); // Warning: non-blocking + int receive_buffer_size() const { return m_receive_buffer_size; } + + bool null_buffers() const { return m_null_buffers; } + +private: + + // it's important that these match the enums in performance_counters for + // num_utp_idle etc. + enum class state_t { + // not yet connected + none, + // sent a syn packet, not received any acks + syn_sent, + // syn-ack received and in normal operation + // of sending and receiving data + 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 + 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. + error_wait, + + // there are no more references to this socket + // and we can delete it + deleting + }; + + packet_ptr acquire_packet(int const allocate); + void release_packet(packet_ptr p); + + void set_state(state_t s); + state_t state() const { return static_cast(m_state); } + +#if TORRENT_USE_INVARIANT_CHECKS + void check_receive_buffers() const; + void check_invariant() const; +#endif + + utp_socket_manager& m_sm; + + // userdata pointer passed along + // with any callback. This is initialized to nullptr + // 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 state_t::deleting state + utp_stream* m_userdata; + + // 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 state_t::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; + + // 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; +}; + +} +} + +#endif diff --git a/include/libtorrent/aux_/vector.hpp b/include/libtorrent/aux_/vector.hpp new file mode 100644 index 0000000..2ed7ab0 --- /dev/null +++ b/include/libtorrent/aux_/vector.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2016, 2018, 2020, Arvid Norberg +Copyright (c) 2017, 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_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_cng.hpp b/include/libtorrent/aux_/win_cng.hpp new file mode 100644 index 0000000..09b36da --- /dev/null +++ b/include/libtorrent/aux_/win_cng.hpp @@ -0,0 +1,208 @@ +/* + +Copyright (c) 2019, Andrei Kurushin +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_WIN_CNG_HPP +#define TORRENT_WIN_CNG_HPP + +#include + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_CNG +#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 void throw_ntstatus_error(char const* name, NTSTATUS status) { + throw_ex(status, system_category(), name); + } + + inline BCRYPT_ALG_HANDLE cng_open_algorithm_handle(LPCWSTR alg_name) + { + BCRYPT_ALG_HANDLE algorithm_handle{ 0 }; + NTSTATUS status = + BCryptOpenAlgorithmProvider(&algorithm_handle, alg_name, nullptr, 0); + if (status < 0) { + throw_ntstatus_error("BCryptOpenAlgorithmProvider", status); + } + return algorithm_handle; + } + + inline DWORD cng_get_algorithm_object_size( + BCRYPT_ALG_HANDLE algorithm_handle) + { + DWORD object_size{ 0 }; + DWORD data_size{ 0 }; + NTSTATUS status = BCryptGetProperty(algorithm_handle, + BCRYPT_OBJECT_LENGTH, (PBYTE)&object_size, sizeof(DWORD), + &data_size, 0); + if (status < 0) { + throw_ntstatus_error("BCryptGetProperty BCRYPT_OBJECT_LENGTH", + status); + } + + return object_size; + } + + inline void cng_gen_random(span buffer) + { + static BCRYPT_ALG_HANDLE algorithm_handle = + cng_open_algorithm_handle(BCRYPT_RNG_ALGORITHM); + + NTSTATUS status = BCryptGenRandom(algorithm_handle, + reinterpret_cast(buffer.data()), + static_cast(buffer.size()), 0); + if (status < 0) { + throw_ntstatus_error("BCryptGenRandom", status); + } + } + + template + struct cng_hash + { + cng_hash() { create(); } + cng_hash(cng_hash const& h) { duplicate(h); } + ~cng_hash() + { + destroy(); + } + + cng_hash& operator=(cng_hash const& h) & + { + if (this == &h) return *this; + if (m_hash == h.m_hash) return *this; + destroy(); + duplicate(h); + return *this; + } + + void reset() + { + destroy(); + create(); + } + + void update(span data) + { + NTSTATUS status = BCryptHashData( + m_hash, + (PUCHAR)(data.data()), + static_cast(data.size()), 0); + if (status < 0) { + throw_ntstatus_error("BCryptHashData", status); + } + } + + void get_hash(char *digest, std::size_t digest_size) + { + NTSTATUS status = BCryptFinishHash(m_hash, + reinterpret_cast(digest), + static_cast(digest_size), 0); + if (status < 0) { + throw_ntstatus_error("BCryptFinishHash", status); + } + } + private: + void create() + { + NTSTATUS status = BCryptCreateHash(get_algorithm_handle(), + &m_hash, m_hash_object.data(), m_hash_object.size(), + nullptr, 0, 0); + if (status < 0) { + throw_ntstatus_error("BCryptCreateHash", status); + } + } + + void destroy() + { + NTSTATUS status = BCryptDestroyHash(m_hash); + if (status < 0) { + throw_ntstatus_error("BCryptDestroyHash", status); + } + } + + void duplicate(cng_hash const& h) + { + NTSTATUS status = BCryptDuplicateHash(h.m_hash, + &m_hash, m_hash_object.data(), m_hash_object.size(), 0); + if (status < 0) { + throw_ntstatus_error("BCryptDuplicateHash", status); + } + } + + BCRYPT_ALG_HANDLE get_algorithm_handle() + { + static BCRYPT_ALG_HANDLE algorithm_handle = + cng_open_algorithm_handle(AlgId::name); + return algorithm_handle; + } + + std::size_t get_algorithm_object_size() + { + static std::size_t object_size = + static_cast( + cng_get_algorithm_object_size(get_algorithm_handle())); + return object_size; + } + + using hash_object_t = std::vector; + + BCRYPT_HASH_HANDLE m_hash; + hash_object_t m_hash_object + = hash_object_t(get_algorithm_object_size()); + }; + + struct cng_sha1_algorithm { + static constexpr LPCWSTR name = BCRYPT_SHA1_ALGORITHM; + }; + + struct cng_sha256_algorithm { + static constexpr LPCWSTR name = BCRYPT_SHA256_ALGORITHM; + }; + + struct cng_sha512_algorithm { + static constexpr LPCWSTR name = BCRYPT_SHA512_ALGORITHM; + }; + +} // namespace aux +} // namespace libtorrent + +#endif // TORRENT_USE_CNG + +#endif // TORRENT_WIN_CNG_HPP diff --git a/include/libtorrent/aux_/win_crypto_provider.hpp b/include/libtorrent/aux_/win_crypto_provider.hpp new file mode 100644 index 0000000..c320be4 --- /dev/null +++ b/include/libtorrent/aux_/win_crypto_provider.hpp @@ -0,0 +1,148 @@ +/* + +Copyright (c) 2017, Alden Torres +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2017, 2019, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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" + +#if TORRENT_USE_CRYPTOAPI +#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 // TORRENT_USE_CRYPTOAPI + +#endif // TORRENT_WIN_CRYPTO_PROVIDER_HPP diff --git a/include/libtorrent/aux_/win_util.hpp b/include/libtorrent/aux_/win_util.hpp new file mode 100644 index 0000000..44696ae --- /dev/null +++ b/include/libtorrent/aux_/win_util.hpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Tiger Wang +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 { + +#ifdef TORRENT_WINRT + using LoadLibraryASignature = HMODULE WINAPI(_In_ LPCSTR lpLibFileName); +#endif + + template + HMODULE get_library_handle() + { + static bool handle_checked = false; + static HMODULE handle = nullptr; + + if (!handle_checked) + { + handle_checked = true; + +#ifdef TORRENT_WINRT + MEMORY_BASIC_INFORMATION Information; + + if (::VirtualQuery(&VirtualQuery, &Information, sizeof(Information)) == 0) + { + return nullptr; + } + + const auto SyscallBegin = static_cast(Information.AllocationBase); + const auto LoadLibraryA = reinterpret_cast(::GetProcAddress(SyscallBegin, "LoadLibraryA")); + + if (LoadLibraryA == nullptr) + { + return nullptr; + } +#endif + + handle = LoadLibraryA(Library::library_name); + } + 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..acb2755 --- /dev/null +++ b/include/libtorrent/aux_/windows.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2018, Arvid Norberg, Steven Siloti +Copyright (c) 2018, 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_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/bdecode.hpp b/include/libtorrent/bdecode.hpp new file mode 100644 index 0000000..7bde991 --- /dev/null +++ b/include/libtorrent/bdecode.hpp @@ -0,0 +1,466 @@ +/* + +Copyright (c) 2015-2016, Alden Torres +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 aux { + +// 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_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. + // the ``data_offset()`` function returns the byte-offset to this node in, + // starting from the beginning of the buffer that was parsed. + span data_section() const noexcept; + std::ptrdiff_t data_offset() 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 ``string_view`` 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). + // + // ``dict_at()`` returns the (key, value)-pair at the specified index in a + // dictionary. Keys are only allowed to be strings. ``dict_at_node()`` also + // returns the (key, value)-pair, but the key is returned as a + // ``bdecode_node`` (and it will always be a string). + bdecode_node dict_find(string_view key) const; + std::pair dict_at(int i) const; + std::pair dict_at_node(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_offset()`` returns the byte offset from the start of the parsed + // bencoded buffer this string can be found. + string_view string_value() const; + char const* string_ptr() const; + int string_length() const; + std::ptrdiff_t string_offset() 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(aux::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 + aux::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..dd78725 --- /dev/null +++ b/include/libtorrent/bencode.hpp @@ -0,0 +1,408 @@ +/* + +Copyright (c) 2003-2005, 2007-2009, 2012-2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2019, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 aux { + + 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"); + std::array buf; + 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 aux::bencode_recursive(out, e); + } + +#if TORRENT_ABI_VERSION == 1 + template + TORRENT_DEPRECATED + entry bdecode(InIt start, InIt end) + { + entry e; + bool err = false; + aux::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; + aux::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..fd08842 --- /dev/null +++ b/include/libtorrent/bitfield.hpp @@ -0,0 +1,326 @@ +/* + +Copyright (c) 2008-2009, 2012-2019, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Falcosc +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/bloom_filter.hpp b/include/libtorrent/bloom_filter.hpp new file mode 100644 index 0000000..ce062a7 --- /dev/null +++ b/include/libtorrent/bloom_filter.hpp @@ -0,0 +1,80 @@ +/* + +Copyright (c) 2010-2011, 2015-2017, 2019, Arvid Norberg +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_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* k, std::uint8_t* bits, int len); + TORRENT_EXTRA_EXPORT bool has_bits(std::uint8_t const* k, 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 + { + int const c = (std::min)(count_zero_bits(bits, N), (N * 8) - 1); + int const 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/bt_peer_connection.hpp b/include/libtorrent/bt_peer_connection.hpp new file mode 100644 index 0000000..776ebed --- /dev/null +++ b/include/libtorrent/bt_peer_connection.hpp @@ -0,0 +1,519 @@ +/* + +Copyright (c) 2007, Un Shyam +Copyright (c) 2006-2020, Arvid Norberg +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2018-2019, Steven Siloti +Copyright (c) 2018, Greg Hazel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/aux_/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" +#include "libtorrent/hash_picker.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 + + struct TORRENT_EXTRA_EXPORT bt_peer_connection + : peer_connection + { + friend struct invariant_access; + + // 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& 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, + + msg_hash_request = 21, + msg_hashes, + msg_hash_reject, + + 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); + void on_hash_request(int received); + void on_hashes(int received); + void on_hash_reject(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(); + + template + void extension_notify(F message, Args... args); + + // 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); + void write_hash_request(hash_request const& req); + void write_hashes(hash_request const& req, span hashes); + void write_hash_reject(hash_request const& req, sha256_hash const& file_root); + + void maybe_send_hash_request(); + + // 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, (aux::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 + aux::buffer buf(size, {holder.data(), size}); + append_send_buffer(std::move(buf), size); + } + else +#endif + { + append_send_buffer(std::move(holder), size); + } + } + + private: + +#if !defined TORRENT_DISABLE_ENCRYPTION + void init_bt_handshake(); +#endif + + 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, +#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 + aux::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; + + std::vector m_hash_requests; + +#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/choker.hpp b/include/libtorrent/choker.hpp new file mode 100644 index 0000000..e593ca0 --- /dev/null +++ b/include/libtorrent/choker.hpp @@ -0,0 +1,60 @@ +/* + +Copyright (c) 2014, 2017-2020, Arvid Norberg +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_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; +} + struct 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 + , time_duration unchoke_interval + , aux::session_settings const& sett); + +} + +#endif // TORRENT_CHOKER_INCLUDED diff --git a/include/libtorrent/client_data.hpp b/include/libtorrent/client_data.hpp new file mode 100644 index 0000000..885fdd4 --- /dev/null +++ b/include/libtorrent/client_data.hpp @@ -0,0 +1,104 @@ +/* + +Copyright (c) 2019-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_CLIENT_DATA_HPP_INCLUDED +#define TORRENT_CLIENT_DATA_HPP_INCLUDED + +#include "libtorrent/aux_/export.hpp" +#include + +namespace libtorrent { + +// A thin wrapper around a void pointer used as "user data". i.e. an opaque +// cookie passed in to libtorrent and returned on demand. It adds type-safety by +// requiring the same type be requested out of it as was assigned to it. +struct TORRENT_EXPORT client_data_t +{ + // construct a nullptr client data + client_data_t() = default; + + // initialize the client data with the specified pointer + template + explicit client_data_t(T* v) + : m_type_ptr(type()) + , m_client_ptr(v) + {} + + // assigns a new pointer to the client data + template + client_data_t& operator=(T* v) + { + m_type_ptr = type(); + m_client_ptr = v; + return *this; + } + + // request to retrieve the pointer back again. The type ``T`` must be + // identical to the type of the pointer assigned earlier, including + // cv-qualifiers. + template + T* get() const + { + if (m_type_ptr != type()) return nullptr; + return static_cast(m_client_ptr); + } + template ::value>::type> + explicit operator T() const + { + if (m_type_ptr != type::type>()) return nullptr; + return static_cast(m_client_ptr); + } + +#if TORRENT_ABI_VERSION > 2 + // we don't allow type-unsafe operations + operator void*() const = delete; + operator void const*() const = delete; + client_data_t& operator=(void*) = delete; + client_data_t& operator=(void const*) = delete; +#endif + +private: + template + char const* type() const + { + // each unique T will instantiate a unique "instance", and have a unique + // address + static const char instance = 0; + return &instance; + } + char const* m_type_ptr = nullptr; + void* m_client_ptr = nullptr; +}; + +} + +#endif diff --git a/include/libtorrent/close_reason.hpp b/include/libtorrent/close_reason.hpp new file mode 100644 index 0000000..8a22b29 --- /dev/null +++ b/include/libtorrent/close_reason.hpp @@ -0,0 +1,156 @@ +/* + +Copyright (c) 2015, 2017, 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_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..6335dfc --- /dev/null +++ b/include/libtorrent/config.hpp @@ -0,0 +1,656 @@ +/* + +Copyright (c) 2005, 2008-2020, Arvid Norberg +Copyright (c) 2015, John Sebastian Peterson +Copyright (c) 2016, terry zhao +Copyright (c) 2016-2017, 2019, Alden Torres +Copyright (c) 2016, 2019, Andrei Kurushin +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, Pavel Pimenov +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 "libtorrent/aux_/disable_warnings_push.hpp" + +#define _FILE_OFFSET_BITS 64 + +#include + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.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) + +#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 + +// ==== Darwin/BSD === +#elif (defined __APPLE__ && defined __MACH__) || defined __FreeBSD__ || defined __NetBSD__ \ + || defined __OpenBSD__ || defined __bsdi__ || defined __DragonFly__ \ + || defined __FreeBSD_kernel__ +#define TORRENT_BSD + +#if defined __APPLE__ + +#include +#include + +#if defined __MACH__ && MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +#define TORRENT_HAS_COPYFILE 1 +#endif + +#define TORRENT_NATIVE_UTF8 1 + +#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 +#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 + +#define TORRENT_USE_DEV_RANDOM 1 + +#else + +// non-Apple BSD +#define TORRENT_USE_GETRANDOM 1 +#define TORRENT_HAS_PTHREAD_SET_NAME 1 + +#endif // __APPLE__ + +#define TORRENT_HAS_SYMLINK 1 + +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 1 +#endif +#define TORRENT_USE_MADVISE 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 + +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 1 +#endif + +#if defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 27)) +#define TORRENT_HAS_COPY_FILE_RANGE 1 +#endif + +#define TORRENT_HAS_PTHREAD_SET_NAME 1 +#define TORRENT_HAS_SYMLINK 1 +#define TORRENT_USE_MADVISE 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 + +#if defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ > 24)) +#define TORRENT_USE_GETRANDOM 1 +#endif + +// ===== ANDROID ===== (almost linux, sort of) +#if defined __ANDROID__ +#define TORRENT_ANDROID +#define TORRENT_HAS_PTHREAD_SET_NAME 1 + +#if __ANDROID_API__ < 21 +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_HAS_FADVISE 0 +#endif // API < 21 + +// android 32 bits has real problems with fseeko +#if (__ANDROID_API__ < 24) || defined __arm__ || defined __i386__ +#define TORRENT_HAS_FSEEKO 0 +#endif + +#if __ANDROID_API__ < 24 +#define TORRENT_HAS_FTELLO 0 +#endif // API < 24 + +// Starting Android 11 (API >= 30), the enum_routes using NETLINK +// is not possible anymore. For other functions, it's not clear +// that IFADDRS is working as expected for API >= 30, but at least +// it is supported. +// See https://developer.android.com/training/articles/user-data-ids#mac-11-plus +#if __ANDROID_API__ >= 24 +#undef TORRENT_USE_NETLINK +#undef TORRENT_USE_IFADDRS +#define TORRENT_USE_NETLINK 0 +#define TORRENT_USE_IFADDRS 1 +#endif // API >= 24 + +#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_HAVE_MAP_VIEW_OF_FILE +#define TORRENT_HAVE_MAP_VIEW_OF_FILE 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 + +// mingw doesn't implement random_device. +#define TORRENT_BROKEN_RANDOM_DEVICE 1 + +# if !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#ifdef NTDDI_VERSION +# if (NTDDI_VERSION >= NTDDI_VISTA) +# define TORRENT_USE_CNG 1 +# endif +#else // NTDDI_VERSION not defined so use simple _WIN32_WINNT check +# if _WIN32_WINNT >= 0x0600 +# define TORRENT_USE_CNG 1 +# endif +#endif + +# if !defined TORRENT_USE_CNG +// unless some other crypto library has been specified, default to the native +// windows CryptoAPI +#define TORRENT_USE_CRYPTOAPI 1 +#define TORRENT_USE_DEV_RANDOM 0 + +#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 // !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#endif +// ==== WINDOWS === +#elif defined _WIN32 +#define TORRENT_WINDOWS +#ifndef TORRENT_USE_GETIPFORWARDTABLE +# define TORRENT_USE_GETIPFORWARDTABLE 1 +#endif + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifndef TORRENT_HAVE_MAP_VIEW_OF_FILE +#define TORRENT_HAVE_MAP_VIEW_OF_FILE 1 +#endif + +# if !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#ifdef NTDDI_VERSION +# if (NTDDI_VERSION >= NTDDI_VISTA) +# define TORRENT_USE_CNG 1 +# endif +#else // NTDDI_VERSION not defined so use simple _WIN32_WINNT check +# if _WIN32_WINNT >= 0x0600 +# define TORRENT_USE_CNG 1 +# endif +#endif + +# if !defined TORRENT_USE_CNG +// unless some other crypto library has been specified, default to the native +// windows CryptoAPI +#define TORRENT_USE_CRYPTOAPI 1 +#define TORRENT_USE_DEV_RANDOM 0 + +#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 // !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#endif + +#define TORRENT_USE_GETADAPTERSADDRESSES 1 +#define TORRENT_HAS_SALEN 0 +#define TORRENT_USE_RLIMIT 0 +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_USE_UNC_PATHS 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 +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 1 +#endif +#define TORRENT_USE_MADVISE 1 +#define TORRENT_HAS_SYMLINK 1 +#define TORRENT_USE_GETRANDOM 1 + +// ==== BEOS === +#elif defined __BEOS__ || defined __HAIKU__ +#define TORRENT_BEOS +#include // B_PATH_NAME_LENGTH +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_NATIVE_UTF8 1 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_USE_GRTTABLE 1 + +// ==== GNU/Hurd === +#elif defined __GNU__ +#define TORRENT_HURD +#define TORRENT_USE_IFADDRS 1 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_HAS_SYMLINK 1 +#define TORRENT_USE_GETRANDOM 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 + +#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 + +#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 + +#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_HAS_FADVISE +#define TORRENT_HAS_FADVISE 1 +#endif + +#ifndef TORRENT_HAS_FSEEKO +#define TORRENT_HAS_FSEEKO 1 +#endif + +#ifndef TORRENT_HAS_FTELLO +#define TORRENT_HAS_FTELLO 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_CNG +#define TORRENT_USE_CNG 0 +#endif + +#ifndef TORRENT_USE_DEV_RANDOM +#define TORRENT_USE_DEV_RANDOM 1 +#endif + +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 0 +#endif + +#ifndef TORRENT_HAVE_MAP_VIEW_OF_FILE +#define TORRENT_HAVE_MAP_VIEW_OF_FILE 0 +#endif + +#ifndef TORRENT_USE_MADVISE +#define TORRENT_USE_MADVISE 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 + +#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_HAS_SYMLINK +#define TORRENT_HAS_SYMLINK 0 +#endif + +#ifndef TORRENT_USE_IFCONF +#define TORRENT_USE_IFCONF 0 +#endif + +#ifndef TORRENT_USE_GETRANDOM +#define TORRENT_USE_GETRANDOM 0 +#endif + +#ifndef TORRENT_NATIVE_UTF8 +#define TORRENT_NATIVE_UTF8 0 +#endif + +#ifndef TORRENT_HAS_PTHREAD_SET_NAME +#define TORRENT_HAS_PTHREAD_SET_NAME 0 +#endif + +#ifndef TORRENT_HAS_COPY_FILE_RANGE +#define TORRENT_HAS_COPY_FILE_RANGE 0 +#endif + +#ifndef TORRENT_HAS_COPYFILE +#define TORRENT_HAS_COPYFILE 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 + +#if defined TORRENT_USE_OPENSSL || defined TORRENT_USE_GNUTLS +#define TORRENT_USE_SSL 1 +#else +#define TORRENT_USE_SSL 0 +#endif + +#if defined TORRENT_SSL_PEERS && !TORRENT_USE_SSL +#error compiling with TORRENT_SSL_PEERS requires TORRENT_USE_OPENSSL or TORRENT_USE_GNUTLS +#endif + +#include "libtorrent/aux_/export.hpp" + +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..c2cec73 --- /dev/null +++ b/include/libtorrent/copy_ptr.hpp @@ -0,0 +1,68 @@ +/* + +Copyright (c) 2010, 2016-2019, Arvid Norberg +Copyright (c) 2017, Matthew Fioravante +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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..300fb8e --- /dev/null +++ b/include/libtorrent/crc32c.hpp @@ -0,0 +1,46 @@ +/* + +Copyright (c) 2010, 2014, 2016-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_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); + TORRENT_EXTRA_EXPORT std::uint32_t crc32c(std::uint64_t const*, int); +} + +#endif diff --git a/include/libtorrent/create_torrent.hpp b/include/libtorrent/create_torrent.hpp new file mode 100644 index 0000000..dc65586 --- /dev/null +++ b/include/libtorrent/create_torrent.hpp @@ -0,0 +1,532 @@ +/* + +Copyright (c) 2008-2020, Arvid Norberg +Copyright (c) 2016, Markus +Copyright (c) 2016-2017, 2019, Alden Torres +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_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/hasher.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/session_params.hpp" // for disk_io_constructor_type +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/path.hpp" // for combine_path etc. +#include "libtorrent/fwd.hpp" +#include "libtorrent/aux_/throw.hpp" + +#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 { + + // 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 + +#if TORRENT_ABI_VERSION <= 2 + // 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. + // incompatible with v2 metadata, ignored + TORRENT_DEPRECATED static constexpr create_flags_t optimize_alignment = 0_bit; +#endif +#if TORRENT_ABI_VERSION == 1 + // same as optimize_alignment, for backwards compatibility + TORRENT_DEPRECATED static constexpr create_flags_t optimize = 0_bit; +#endif + +#if TORRENT_ABI_VERSION <= 2 + // 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. + // support for BEP 30 merkle torrents has been removed + TORRENT_DEPRECATED static constexpr create_flags_t merkle = 1_bit; +#endif + + // 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. +#if TORRENT_ABI_VERSION <= 2 + // BEP 52 requires files to be piece aligned so all torrents are now compatible + // with BEP 38 + TORRENT_DEPRECATED static constexpr create_flags_t mutable_torrent_support = 4_bit; +#endif + + // Do not generate v1 metadata. The resulting torrent will only be usable by + // clients which support v2. This requires setting all v2 hashes, with + // set_hash2() before calling generate(). Setting v1 hashes (with + // set_hash()) is an error with this flag set. + static constexpr create_flags_t v2_only = 5_bit; + + // do not generate v2 metadata or enforce v2 alignment and padding rules + // this is mainly for tests, not recommended for production use. This + // requires setting all v1 hashes, with set_hash(), before calling + // generate(). Setting v2 hashes (with set_hash2()) is an error with + // this flag set. + static constexpr create_flags_t v1_only = 6_bit; + + // This flag only affects v1-only torrents, and is only relevant + // together with the v1_only_flag. This flag will force the + // same file order and padding as a v2 (or hybrid) torrent would have. + // It has the effect of ordering files and inserting pad files to align + // them with piece boundaries. + static constexpr create_flags_t canonical_files = 7_bit; + + // passing this flag to add_files() will ignore file attributes (such as + // executable or hidden) when adding the files to the file storage. + // Since not all filesystems and operating systems support all file + // attributes the resulting torrent may differ depending on where it's + // created. If it's important for torrents to be created consistently + // across systems, this flag should be set. + static constexpr create_flags_t no_attributes = 8_bit; + + // The ``piece_size`` is the size of each piece in bytes. It must be a + // power of 2 and a minimum of 16 kiB. If a piece size of 0 is + // specified, a piece_size will be set automatically. + // + // The file_storage (``fs``) parameter defines the files, sizes and + // their properties for the torrent to be created. Set this up first, + // before passing it to the create_torrent constructor. + // + // 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. + // + // .. warning:: + // The file_storage and torrent_info objects must stay alive for the + // entire duration of the create_torrent object. + // + // The ``flags`` arguments specifies options for the torrent creation. It can + // be any combination of the flags defined by create_flags_t. + explicit create_torrent(file_storage& fs, int piece_size = 0 + , create_flags_t flags = {}); + explicit create_torrent(torrent_info const& ti); + +#if TORRENT_ABI_VERSION <= 2 + TORRENT_DEPRECATED + explicit create_torrent(file_storage& fs, int piece_size + , int, create_flags_t flags = {}, int = -1) + : create_torrent(fs, piece_size, flags) {} +#endif + + // 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. + // + // Whether the resulting torrent object is v1, v2 or hybrid depends on + // whether any of the v1_only or v2_only flags were set on the + // constructor. If neither were set, the resulting torrent depends on + // which hashes were set. If both v1 and v2 hashes were set, a hybrid + // torrent is created. + // + // Any failure will cause this function to throw system_error, with an + // appropriate error message. These are the reasons this call may throw: + // + // * the file storage has 0 files + // * the total size of the file storage is 0 bytes (i.e. it only has + // empty files) + // * not all v1 hashes (set_hash()) and not all v2 hashes (set_hash2()) + // were set + // * for v2 torrents, you may not have a directory with the same name as + // a file. If that's encountered in the file storage, generate() + // fails. + 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); + + // sets the "creation time" field. Defaults to the system clock at the + // time of construction of the create_torrent object. The timestamp is + // specified in seconds, posix time. If the creation date is set to 0, + // the "creation date" field will be omitted from the generated torrent. + void set_creation_date(std::time_t timestamp); + + // 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(). + // A SHA-1 hash of all zeros is internally used to indicate a hash that + // has not been set. Setting such hash will not be considered set when + // calling generate(). + // This function will throw ``std::system_error`` if it is called on an + // object constructed with the v2_only flag. + void set_hash(piece_index_t index, sha1_hash const& h); + + // sets the bittorrent v2 hash for file `file` of the piece `piece`. + // `piece` is relative to the first piece of the file, starting at 0. The + // first piece in the file can be computed with + // file_storage::file_index_at_piece(). + // The hash, `h`, is the root of the merkle tree formed by the piece's + // 16 kiB blocks. Note that piece sizes must be powers-of-2, so all + // per-piece merkle trees are complete. + // A SHA-256 hash of all zeros is internally used to indicate a hash + // that has not been set. Setting such hash will not be considered set + // when calling generate(). + // This function will throw ``std::system_error`` if it is called on an + // object constructed with the v1_only flag. + void set_hash2(file_index_t file, piece_index_t::diff_type piece, sha256_hash const& h); + +#if TORRENT_ABI_VERSION < 3 + // 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. + // .. note:: + // + // with bittorrent v2, this feature is obsolete + TORRENT_DEPRECATED + void set_file_hash(file_index_t index, sha1_hash const& h); +#endif + + // 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 cert); + + // 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; } + + bool is_v2_only() const { return m_v2_only; } + bool is_v1_only() const { return m_v1_only; } + + // 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); } + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // 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. + TORRENT_DEPRECATED + std::vector merkle_tree() const { return std::vector(); } +#endif + + // 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`_. + void add_similar_torrent(sha1_hash ih); + void add_collection(string_view c); + + private: + + file_storage const& 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; + + // leave this here for now, to preserve ABI between building with + // deprecated functions and without + aux::vector m_filehashes; + + mutable aux::vector m_fileroots; + aux::vector, file_index_t> m_file_piece_hash; + + std::vector m_similar; + std::vector m_collections; + + // 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, 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; + + bool m_v2_only:1; + + // only generate v1 metadata and do not enforce v2 padding rules + bool m_v1_only:1; + }; + +namespace aux { + 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 taking a settings_pack may be used to configure the + // underlying disk access. Such as ``settings_pack::aio_threads``. + // + // 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); + TORRENT_EXPORT void set_piece_hashes(create_torrent& t, std::string const& p + , settings_interface const& settings + , std::function const& f, error_code& ec); + TORRENT_EXPORT void set_piece_hashes(create_torrent& t, std::string const& p + , settings_interface const& settings, disk_io_constructor_type disk_io + , 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, aux::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, aux::nop, ec); + if (ec) aux::throw_ex(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) aux::throw_ex(ec); + } + inline void set_piece_hashes(create_torrent& t, std::string const& p + , settings_interface const& settings + , std::function const& f) + { + error_code ec; + set_piece_hashes(t, p, settings, f, ec); + if (ec) aux::throw_ex(ec); + } +#endif + +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..723ed9c --- /dev/null +++ b/include/libtorrent/deadline_timer.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2009, 2015, 2017-2019, Arvid Norberg +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_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..0211d98 --- /dev/null +++ b/include/libtorrent/debug.hpp @@ -0,0 +1,288 @@ +/* + +Copyright (c) 2003, 2010, 2012-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2017, 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_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 +#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 + TORRENT_EXTRA_EXPORT extern std::map _async_ops; + TORRENT_EXTRA_EXPORT extern int _async_ops_nthreads; + TORRENT_EXTRA_EXPORT extern std::mutex _async_ops_mutex; + + // timestamp -> operation + struct wakeup_t + { + time_point timestamp; + std::uint64_t context_switches; + char const* operation; + }; + TORRENT_EXTRA_EXPORT 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 (auto const& op : _async_ops) + { + if (op.second.refs <= _async_ops_nthreads - 1) continue; + ret += op.second.refs; + std::printf("%s: (%d)\n%s\n", op.first.c_str(), op.second.refs, op.second.stack.c_str()); + } + return ret; + } + + struct handler_alloc_t + { + std::size_t capacity; + std::set> allocations; + }; + // defined in session_impl.cpp + extern std::map _handler_storage; + extern std::mutex _handler_storage_mutex; + extern bool _handler_logger_registered; + + inline void log_handler_allocators() noexcept + { + static char const* const handler_names[] = { + "write_handler", + "read_handler", + "udp_handler", + "tick_handler", + "abort_handler", + "defer_handler", + "utp_handler", + "submit_handler", + }; + std::lock_guard l(_handler_storage_mutex); + std::printf("handler allocator storage:\n\n"); + for (auto const& e : _handler_storage) + { + std::size_t allocated = 0; + std::string handler_name; + // pick the largest allocation, in case the storage was used for + // different handlers + for (auto const& a : e.second.allocations) + { + if (allocated >= a.second) continue; + allocated = a.second; + handler_name = demangle(e.second.allocations.begin()->first->name()); + } + + std::printf("%15s: capacity: %-3d allocated: %-3d handler: %s\n" + , handler_names[e.first], int(e.second.capacity), int(allocated), handler_name.c_str()); + } + } + + template + void record_handler_allocation(int const type, std::size_t const capacity) + { + std::lock_guard l(_handler_storage_mutex); + auto& e = _handler_storage[type]; + e.capacity = capacity; + e.allocations.emplace(&typeid(Handler), sizeof(Handler)); + if (!_handler_logger_registered) + { + std::atexit(&log_handler_allocators); + _handler_logger_registered = true; + } + } +} + +#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/disabled_disk_io.hpp b/include/libtorrent/disabled_disk_io.hpp new file mode 100644 index 0000000..4487b92 --- /dev/null +++ b/include/libtorrent/disabled_disk_io.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2016, 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_DISABLED_DISK_IO +#define TORRENT_DISABLED_DISK_IO + +#include "libtorrent/config.hpp" +#include "libtorrent/io_context.hpp" + +#include + +namespace libtorrent { + + struct counters; + struct disk_interface; + struct settings_interface; + + // creates a disk io object that discards all data written to it, and only + // returns zero-buffers when read from. May be useful for testing and + // benchmarking. + TORRENT_EXPORT std::unique_ptr disabled_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); +} + +#endif + diff --git a/include/libtorrent/disk_buffer_holder.hpp b/include/libtorrent/disk_buffer_holder.hpp new file mode 100644 index 0000000..b88de7a --- /dev/null +++ b/include/libtorrent/disk_buffer_holder.hpp @@ -0,0 +1,112 @@ +/* + +Copyright (c) 2008-2009, 2013-2019, Arvid Norberg +Copyright (c) 2018, 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_DISK_BUFFER_HOLDER_HPP_INCLUDED +#define TORRENT_DISK_BUFFER_HOLDER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include + +namespace libtorrent { + + // the interface for freeing disk buffers, used by the disk_buffer_holder. + // when implementing disk_interface, this must also be implemented in order + // to return disk buffers back to libtorrent + struct TORRENT_EXPORT buffer_allocator_interface + { + virtual void free_disk_buffer(char* b) = 0; + protected: + ~buffer_allocator_interface() = default; + }; + + // The disk buffer holder acts like a ``unique_ptr`` that frees a disk buffer + // when it's destructed + // + // If this buffer holder is moved-from, default constructed or reset, + // ``data()`` will return nullptr. + struct TORRENT_EXPORT disk_buffer_holder + { + 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 + , char* buf, int sz) noexcept; + + // default construct a holder that does not own any buffer + disk_buffer_holder() noexcept = default; + + // frees disk buffer held by this object + ~disk_buffer_holder(); + + // return a pointer to the held buffer, if any. Otherwise returns nullptr. + char* data() const noexcept { return m_buf; } + + // free the held disk buffer, if any, and clear the holder. This sets the + // holder object to a default-constructed state + void reset(); + + // swap pointers of two disk buffer holders. + void swap(disk_buffer_holder& h) noexcept + { + using std::swap; + swap(h.m_allocator, m_allocator); + swap(h.m_buf, m_buf); + swap(h.m_size, m_size); + } + + // if this returns true, the buffer may not be modified in place + bool is_mutable() const noexcept { return false; } + + // implicitly convertible to true if the object is currently holding a + // buffer + explicit operator bool() const noexcept { return m_buf != nullptr; } + + std::ptrdiff_t size() const { return m_size; } + + private: + + buffer_allocator_interface* m_allocator = nullptr; + char* m_buf = nullptr; + int m_size = 0; + }; + +} + +#endif diff --git a/include/libtorrent/disk_interface.hpp b/include/libtorrent/disk_interface.hpp new file mode 100644 index 0000000..7d3246c --- /dev/null +++ b/include/libtorrent/disk_interface.hpp @@ -0,0 +1,425 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2018, 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_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" + +// OVERVIEW +// +// The disk I/O can be customized in libtorrent. In previous versions, the +// customization was at the level of each torrent. Now, the customization point +// is at the session level. All torrents added to a session will use the same +// disk I/O subsystem, as determined by the disk_io_constructor (in +// session_params). +// +// This allows the disk subsystem to also customize threading and disk job +// management. +// +// To customize the disk subsystem, implement disk_interface and provide a +// factory function to the session constructor (via session_params). +// +// Example use: +// +// .. include:: ../examples/custom_storage.cpp +// :code: c++ +// :tab-width: 2 +// :start-after: -- example begin +// :end-before: // -- example end +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 locked TORRENT_DEPRECATED = 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. 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; + }; + + using disk_job_flags_t = flags::bitfield_flag; + + // The disk_interface is the customization point for disk I/O in libtorrent. + // implement this interface and provide a factory function to the session constructor + // use custom disk I/O. All functions on the disk subsystem (implementing + // disk_interface) are called from within libtorrent's network thread. For + // disk I/O to be performed in a separate thread, the disk subsystem has to + // manage that itself. + // + // Although the functions are called ``async_*``, they do not technically + // *have* to be asynchronous, but they support being asynchronous, by + // expecting the result passed back into a callback. The callbacks must be + // posted back onto the network thread via the io_context object passed into + // the constructor. The callbacks will be run in the network thread. + struct TORRENT_EXPORT disk_interface + { + // force making a copy of the cached block, rather than getting a + // reference to a block already in the cache. This is used the block is + // expected to be overwritten very soon, by async_write()`, and we need + // access to the previous content. + 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. This is a hint that this block is + // unlikely to be read again anytime soon, and caching it would be + // wasteful. + static constexpr disk_job_flags_t volatile_read = 4_bit; + + // compute a v1 piece hash. This is only used by the async_hash() call. + // If this flag is not set in the async_hash() call, the SHA-1 piece + // hash does not need to be computed. + static constexpr disk_job_flags_t v1_hash = 5_bit; + + // this flag instructs a hash job that we just completed this piece, and + // it should be flushed to disk + static constexpr disk_job_flags_t flush_piece = 7_bit; + + // this is called when a new torrent is added. The shared_ptr can be + // used to hold the internal torrent object alive as long as there are + // outstanding disk operations on the storage. + // The returned storage_holder is an owning reference to the underlying + // storage that was just created. It is fundamentally a storage_index_t + virtual storage_holder new_torrent(storage_params const& p + , std::shared_ptr const& torrent) = 0; + + // remove the storage with the specified index. This is not expected to + // delete any files from disk, just to clean up any resources associated + // with the specified storage. + virtual void remove_torrent(storage_index_t) = 0; + + // perform a read or write operation from/to the specified storage + // index and the specified request. When the operation completes, call + // handler possibly with a disk_buffer_holder, holding the buffer with + // the result. Flags may be set to affect the read operation. See + // disk_job_flags_t. + // + // The disk_observer is a callback to indicate that + // the store buffer/disk write queue is below the watermark to let peers + // start writing buffers to disk again. When ``async_write()`` returns + // ``true``, indicating the write queue is full, the peer will stop + // further writes and wait for the passed-in ``disk_observer`` to be + // notified before resuming. + // + // Note that for ``async_read``, the peer_request (``r``) is not + // necessarily aligned to blocks (but it is most of the time). However, + // all writes (passed to ``async_write``) are guaranteed to be block + // aligned. + 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; + + // Compute hash(es) for the specified piece. Unless the v1_hash flag is + // set (in ``flags``), the SHA-1 hash of the whole piece does not need + // to be computed. + // + // The `v2` span is optional and can be empty, which means v2 hashes + // should not be computed. If v2 is non-empty it must be at least large + // enough to hold all v2 blocks in the piece, and this function will + // fill in the span with the SHA-256 block hashes of the piece. + virtual void async_hash(storage_index_t storage, piece_index_t piece, span v2 + , disk_job_flags_t flags + , std::function handler) = 0; + + // computes the v2 hash (SHA-256) of a single block. The block at + // ``offset`` in piece ``piece``. + virtual void async_hash2(storage_index_t storage, piece_index_t piece, int offset, disk_job_flags_t flags + , std::function handler) = 0; + + // called to request the files for the specified storage/torrent be + // moved to a new location. It is the disk I/O object's responsibility + // to synchronize this with any currently outstanding disk operations to + // the storage. Whether files are replaced at the destination path or + // not is controlled by ``flags`` (see move_flags_t). + virtual void async_move_storage(storage_index_t storage, std::string p, move_flags_t flags + , std::function handler) = 0; + + // This is called on disk I/O objects to request they close all open + // files for the specified storage/torrent. If file handles are not + // pooled/cached, it can be a no-op. For truly asynchronous disk I/O, + // this should provide at least one point in time when all files are + // closed. It is possible that later asynchronous operations will + // re-open some of the files, by the time this completion handler is + // called, that's fine. + virtual void async_release_files(storage_index_t storage + , std::function handler = std::function()) = 0; + + // this is called when torrents are added to validate their resume data + // against the files on disk. This function is expected to do a few things: + // + // if ``links`` is non-empty, it contains a string for each file in the + // torrent. The string being a path to an existing identical file. The + // default behavior is to create hard links of those files into the + // storage of the new torrent (specified by ``storage``). An empty + // string indicates that there is no known identical file. This is part + // of the "mutable torrent" feature, where files can be reused from + // other torrents. + // + // The ``resume_data`` points the resume data passed in by the client. + // + // If the ``resume_data->flags`` field has the seed_mode flag set, all + // files/pieces are expected to be on disk already. This should be + // verified. Not just the existence of the file, but also that it has + // the correct size. + // + // Any file with a piece set in the ``resume_data->have_pieces`` bitmask + // should exist on disk, this should be verified. Pad files and files + // with zero priority may be skipped. + virtual void async_check_files(storage_index_t storage + , add_torrent_params const* resume_data + , aux::vector links + , std::function handler) = 0; + + // This is called when a torrent is stopped. It gives the disk I/O + // object an opportunity to flush any data to disk that's currently kept + // cached. This function should at least do the same thing as + // async_release_files(). + virtual void async_stop_torrent(storage_index_t storage + , std::function handler = std::function()) = 0; + + // This function is called when the name of a file in the specified + // storage has been requested to be renamed. The disk I/O object is + // responsible for renaming the file without racing with other + // potentially outstanding operations against the file (such as read, + // write, move, etc.). + virtual void async_rename_file(storage_index_t storage + , file_index_t index, std::string name + , std::function handler) = 0; + + // This function is called when some file(s) on disk have been requested + // to be removed by the client. ``storage`` indicates which torrent is + // referred to. See session_handle for ``remove_flags_t`` flags + // indicating which files are to be removed. + // e.g. session_handle::delete_files - delete all files + // session_handle::delete_partfile - only delete part file. + virtual void async_delete_files(storage_index_t storage, remove_flags_t options + , std::function handler) = 0; + + // This is called to set the priority of some or all files. Changing the + // priority from or to 0 may involve moving data to and from the + // partfile. The disk I/O object is responsible for correctly + // synchronizing this work to not race with any potentially outstanding + // asynchronous operations affecting these files. + // + // ``prio`` is a vector of the file priority for all files. If it's + // shorter than the total number of files in the torrent, they are + // assumed to be set to the default priority. + virtual void async_set_file_priority(storage_index_t storage + , aux::vector prio + , std::function)> handler) = 0; + + // This is called when a piece fails the hash check, to ensure there are + // no outstanding disk operations to the piece before blocks are + // re-requested from peers to overwrite the existing blocks. The disk I/O + // object does not need to perform any action other than synchronize + // with all outstanding disk operations to the specified piece before + // posting the result back. + virtual void async_clear_piece(storage_index_t storage, piece_index_t index + , std::function handler) = 0; + + // update_stats_counters() is called to give the disk storage an + // opportunity to update gauges in the ``c`` stats counters, that aren't + // updated continuously as operations are performed. This is called + // before a snapshot of the counters are passed to the client. + virtual void update_stats_counters(counters& c) const = 0; + + // Return a list of all the files that are currently open for the + // specified storage/torrent. This is is just used for the client to + // query the currently open files, and which modes those files are open + // in. + virtual std::vector get_status(storage_index_t) const = 0; + + // this is called when the session is starting to shut down. The disk + // I/O object is expected to flush any outstanding write jobs, cancel + // hash jobs and initiate tearing down of any internal threads. If + // ``wait`` is true, this should be asynchronous. i.e. this call should + // not return until all threads have stopped and all jobs have either + // been aborted or completed and the disk I/O object is ready to be + // destructed. + virtual void abort(bool wait) = 0; + + // This will be called after a batch of disk jobs has been issues (via + // the ``async_*`` ). It gives the disk I/O object an opportunity to + // notify any potential condition variables to wake up the disk + // thread(s). The ``async_*`` calls can of course also notify condition + // variables, but doing it in this call allows for batching jobs, by + // issuing the notification once for a collection of jobs. + virtual void submit_jobs() = 0; + + // This is called to notify the disk I/O object that the settings have + // been updated. In the disk io constructor, a settings_interface + // reference is passed in. Whenever these settings are updated, this + // function is called to allow the disk I/O object to react to any + // changed settings relevant to its operations. + virtual void settings_updated() = 0; + + // hidden + virtual ~disk_interface() {} + }; + + // a unique, owning, reference to the storage of a torrent in a disk io + // subsystem (class that implements disk_interface). This is held by the + // internal libtorrent torrent object to tie the storage object allocated + // for a torrent to the lifetime of the internal torrent object. When a + // torrent is removed from the session, this holder is destructed and will + // inform the disk object. + struct TORRENT_EXPORT 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 (&rhs == this) return *this; + 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}; + }; + +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/disk_observer.hpp b/include/libtorrent/disk_observer.hpp new file mode 100644 index 0000000..6f54fe1 --- /dev/null +++ b/include/libtorrent/disk_observer.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2010, 2013-2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, 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_DISK_OBSERVER_HPP +#define TORRENT_DISK_OBSERVER_HPP + +#include "libtorrent/config.hpp" + +namespace libtorrent { + + struct TORRENT_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..144dd3e --- /dev/null +++ b/include/libtorrent/download_priority.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2017-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_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/entry.hpp b/include/libtorrent/entry.hpp new file mode 100644 index 0000000..87e0ab6 --- /dev/null +++ b/include/libtorrent/entry.hpp @@ -0,0 +1,334 @@ +/* + +Copyright (c) 2003-2009, 2013-2020, Arvid Norberg +Copyright (c) 2016-2017, 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_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" +#include "libtorrent/aux_/strview_less.hpp" + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + // backwards compatibility + using type_error = system_error; +#endif + struct bdecode_node; + + // 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 = std::map; + 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 + entry(list_type); // NOLINT + entry(integer_type); // NOLINT + entry(preformatted_type); // NOLINT + + // hidden + 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; + } + + // 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. + entry& operator=(bdecode_node const&) &; + entry& operator=(entry const&) &; + entry& operator=(entry&&) & noexcept; + entry& operator=(dictionary_type) &; + entry& operator=(span) &; + entry& operator=(list_type) &; + entry& operator=(integer_type) &; + entry& operator=(preformatted_type) &; + + // hidden + 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; + } + + // 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(); + integer_type const& integer() const; + string_type& string(); + string_type const& string() const; + list_type& list(); + list_type const& list() const; + dictionary_type& dict(); + dictionary_type const& dict() const; + preformatted_type& preformatted(); + preformatted_type const& 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); + entry const& 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: + // hidden + // 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 aux { + + // internal + TORRENT_EXPORT string_view integer_to_str(std::array& 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..c45f8d1 --- /dev/null +++ b/include/libtorrent/enum_net.hpp @@ -0,0 +1,234 @@ +/* + +Copyright (c) 2007-2008, 2010, 2014-2020, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +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_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_context.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 { + + // internal +using interface_flags = flags::bitfield_flag; + +namespace if_flags { + + // internal + 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; +} + +// internal +enum class if_state : std::uint8_t { + + up, + dormant, + lowerlayerdown, + down, + notpresent, + testing, + unknown +}; + +// internal + 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; + }; + +// internal + 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_context& ios + , error_code& ec); + + TORRENT_EXTRA_EXPORT std::vector enum_routes(io_context& 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); + + // returns whether there are *any* routes to the internet in the routing + // table. This can be used to determine if the routing table is fully + // populated or not. + TORRENT_EXTRA_EXPORT bool has_any_internet_route(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_context& 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_context& ios, error_code& ec); + +} + +#endif diff --git a/include/libtorrent/error.hpp b/include/libtorrent/error.hpp new file mode 100644 index 0000000..939d101 --- /dev/null +++ b/include/libtorrent/error.hpp @@ -0,0 +1,53 @@ +/* + +Copyright (c) 2009, 2017, 2019, Arvid Norberg +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_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..2f87f2b --- /dev/null +++ b/include/libtorrent/error_code.hpp @@ -0,0 +1,598 @@ +/* + +Copyright (c) 2008-2011, 2013-2020, Arvid Norberg +Copyright (c) 2016-2017, 2019, Steven Siloti +Copyright (c) 2018, 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_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, + invalid_hash_request, + invalid_hashes, + invalid_hash_reject, + +#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, + // skipped announce (because it's assumed to be unreachable over the + // given source network interface) + announce_skipped, + +#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, + // blocked by SSRF mitigation + ssrf_mitigation, + // blocked because IDNA host names are banned + blocked_by_idna, + + // the torrent file has an unknown meta version + torrent_unknown_version = 210, + // the v2 torrent file has no file tree + torrent_missing_file_tree, + // the torrent contains v2 keys but does not specify meta version 2 + torrent_missing_meta_version, + // the v1 and v2 file metadata does not match + torrent_inconsistent_files, + // one or more files are missing piece layer hashes + torrent_missing_piece_layer, + // a piece layer has the wrong size or failed hash check + torrent_invalid_piece_layer, + // a v2 file entry has no root hash + torrent_missing_pieces_root, + // the v1 and v2 hashes do not describe the same data + torrent_inconsistent_hashes, + // a file in the v2 metadata has the pad attribute set + torrent_invalid_pad_file, + + // 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) {} + storage_error(error_code e, operation_t const op) + : ec(e), file_idx(-1), operation(op) {} + + // 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); } + + private: + // internal + std::int32_t file_idx:24; + + public: + + // 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. + TORRENT_DEPRECATED + char const* operation_str() const + { return operation_name(operation); } +#endif + }; + + // internal + std::string print_error(error_code const&); +} // namespace libtorrent + +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..708c2ed --- /dev/null +++ b/include/libtorrent/extensions.hpp @@ -0,0 +1,548 @@ +/* + +Copyright (c) 2006-2007, 2011, 2013-2020, Arvid Norberg +Copyright (c) 2014-2019, Steven Siloti +Copyright (c) 2016, Alden Torres +Copyright (c) 2018, Greg Hazel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 +// +// 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&, client_data_t); +// +// 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; + +TORRENT_VERSION_NAMESPACE_3 + + // 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 client_data_t 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&, client_data_t) + { return std::shared_ptr(); } + + // called when plugin is added to a session + virtual void added(session_handle const&) {} + + // called when the session is aborted + // the plugin should perform any cleanup necessary to allow the session's + // destruction (e.g. cancel outstanding async operations) + virtual void abort() {} + + // 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(info_hash_t 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)(); } + +#if TORRENT_ABI_VERSION <= 2 + // called when saving settings state + virtual void save_state(entry&) {} + + // called when loading settings state + virtual void load_state(bdecode_node const&) {} +#endif + + virtual std::map save_state() const { return {}; } + + // called on startup while loading settings state from the session_params + virtual void load_state(std::map const&) {} + }; + +TORRENT_VERSION_NAMESPACE_3_END + + 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; } + + virtual void sent_have_all() {} + virtual void sent_have_none() {} + virtual void sent_reject_request(peer_request const&) {} + virtual void sent_allow_fast(piece_index_t) {} + virtual void sent_suggest(piece_index_t) {} + virtual void sent_cancel(peer_request const&) {} + virtual void sent_request(peer_request const&) {} + virtual void sent_choke() {} + // called after a choke message has been sent to the peer + virtual void sent_unchoke() {} + virtual void sent_interested() {} + virtual void sent_not_interested() {} + virtual void sent_have(piece_index_t) {} + virtual void sent_piece(peer_request const&) {} + + // 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..d4acafa --- /dev/null +++ b/include/libtorrent/extensions/smart_ban.hpp @@ -0,0 +1,61 @@ +/* + +Copyright (c) 2007, 2013, 2017, 2019, Arvid Norberg +Copyright (c) 2015, Steven Siloti +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_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; + struct client_data_t; + + // 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&, client_data_t); +} + +#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..57ccfdc --- /dev/null +++ b/include/libtorrent/extensions/ut_metadata.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2007, 2013, 2016-2017, 2019, Arvid Norberg +Copyright (c) 2015, Steven Siloti +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_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; + struct client_data_t; + + // 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&, client_data_t); +} + +#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..2bdc448 --- /dev/null +++ b/include/libtorrent/extensions/ut_pex.hpp @@ -0,0 +1,64 @@ +/* + +Copyright (c) 2006, MassaRoddel +Copyright (c) 2006, 2013, 2015-2017, 2019, Arvid Norberg +Copyright (c) 2015, Steven Siloti +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_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; + struct client_data_t; + + // 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&, client_data_t); +} + +#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..d2acf99 --- /dev/null +++ b/include/libtorrent/file.hpp @@ -0,0 +1,109 @@ +/* + +Copyright (c) 2004, 2008-2010, 2014-2020, Arvid Norberg +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_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/aux_/open_mode.hpp" +#include "libtorrent/flags.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#ifdef TORRENT_WINDOWS +// windows part +#include "libtorrent/aux_/windows.hpp" +#include +#include +#else +// posix part + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#include +#include +#include +#include +#include // for DIR + +#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 + + struct TORRENT_EXTRA_EXPORT file + { + file(); + file(std::string const& p, aux::open_mode_t m, error_code& ec); + file(file&&) noexcept; + file& operator=(file&&); + ~file(); + + file(file const&) = delete; + file& operator=(file const&) = delete; + + std::int64_t writev(std::int64_t file_offset, span bufs + , error_code& ec, aux::open_mode_t flags = {}); + std::int64_t readv(std::int64_t file_offset, span bufs + , error_code& ec, aux::open_mode_t flags = {}); + + private: + handle_type m_file_handle; + }; +} + +#endif // TORRENT_FILE_HPP_INCLUDED diff --git a/include/libtorrent/file_storage.hpp b/include/libtorrent/file_storage.hpp new file mode 100644 index 0000000..bfb31c6 --- /dev/null +++ b/include/libtorrent/file_storage.hpp @@ -0,0 +1,722 @@ +/* + +Copyright (c) 2008-2010, 2012-2020, Arvid Norberg +Copyright (c) 2016, 2020, Alden Torres +Copyright (c) 2017, 2019, 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_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" +#include "libtorrent/error_code.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/disk_interface.hpp" // for default_block_size +#include "libtorrent/fwd.hpp" + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + // information about a file in a file_storage + struct TORRENT_DEPRECATED_EXPORT file_entry + { +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + // 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&&) & = default; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // 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 + +namespace aux { + struct path_index_tag; + using path_index_t = aux::strong_typedef; + + // internal + struct file_entry + { + friend class ::lt::file_storage; + file_entry(); + file_entry(file_entry const& fe); + file_entry& operator=(file_entry const& fe) &; + file_entry(file_entry&& fe) noexcept; + file_entry& operator=(file_entry&& fe) & noexcept; + ~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, + }; + + static constexpr aux::path_index_t no_path{(1 << 30) - 1}; + static constexpr aux::path_index_t path_is_absolute{(1 << 30) - 2}; + + // 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 = nullptr; + public: + // the SHA-256 root of the merkle tree for this file + // this is a pointer into the .torrent file + char const* root = nullptr; + + // 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: + // no_path means no path (i.e. single file torrent) + // path_is_absolute means the filename + // in this field contains the full, absolute path + // to the file + aux::path_index_t path_index = file_entry::no_path; + }; + +} // aux namespace + + // 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 + { + 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&&) &; + + // internal limitations restrict file sizes to not be larger than this + // We use int to index into file merkle trees, so a file may not contain more + // than INT_MAX entries. That means INT_MAX / 2 blocks (leafs) in each + // tree. + static constexpr std::int64_t max_file_size = (std::min)( + (std::int64_t(1) << 48) - 1 + , std::int64_t((std::numeric_limits::max)() / 2) * default_block_size); + static constexpr std::int64_t max_file_offset = (std::int64_t(1) << 48) - 1; + + // 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; + TORRENT_DEPRECATED static constexpr file_flags_t pad_file = 0_bit; + TORRENT_DEPRECATED static constexpr file_flags_t attribute_hidden = 1_bit; + TORRENT_DEPRECATED static constexpr file_flags_t attribute_executable = 2_bit; + TORRENT_DEPRECATED static constexpr file_flags_t 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. + // + // ``root_hash`` is an optional pointer to a 32 byte SHA-256 hash, being + // the merkle tree root hash for this file. This is only used for v2 + // torrents. If the ``root hash`` is specified for one file, it has to + // be specified for all, otherwise this function will fail. + // Note that the buffer ``root_hash`` points to must out-live the + // file_storage object, it will not be copied. This parameter is only + // used when *loading* torrents, that already have their file hashes + // computed. When creating torrents, the file hashes will be computed by + // the piece hashes. + // + // 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``. + // + // The overloads that take an `error_code` reference will report failures + // via that variable, otherwise `system_error` is thrown. +#ifndef BOOST_NO_EXCEPTIONS + 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() + , char const* root_hash = nullptr); + 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() + , char const* root_hash = nullptr); +#endif // BOOST_NO_EXCEPTIONS + void add_file_borrow(error_code& ec, 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() + , char const* root_hash = nullptr); + void add_file(error_code& ec, 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() + , char const* root_hash = nullptr); + + // 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 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + 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 functions depending on aux::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 + aux::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; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#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 + , std::int64_t 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. It must be a power of two + // and at least 16 kiB. + 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; + + // Returns the size of the given piece. If the piece spans multiple files, + // only the first file is considered part of the piece. This is used for + // v2 torrents, where all files are piece aligned and padded. i.e. The pad + // files are not considered part of the piece for this purpose. + int piece_size2(piece_index_t index) const; + + // returns the number of blocks in the specified piece, for v2 torrents. + int blocks_in_piece2(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; + + // arrange files and padding to match the canonical form required + // by BEP 52 + void canonicalize(); + + // 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. + // + // ``root()`` returns the SHA-256 merkle tree root of the specified file, + // in case this is a v2 torrent. Otherwise returns zeros. + // ``root_ptr()`` returns a pointer to the SHA-256 merkle tree root hash + // for the specified file. The pointer points into storage referred to + // when the file was added, it is not owned by this object. Torrents + // that are not v2 torrents return nullptr. + // + // 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; + sha256_hash root(file_index_t index) const; + char const* root_ptr(file_index_t const index) const; + std::string 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 number of pieces or blocks the file at `index` spans, + // under the assumption that the file is aligned to the start of a piece. + // This is only meaningful for v2 torrents, where files are guaranteed + // such alignment. + // These numbers are used to size and navigate the merkle hash tree for + // each file. + int file_num_pieces(file_index_t index) const; + int file_num_blocks(file_index_t index) const; + index_range file_piece_range(file_index_t) const; + + // index of first piece node in the merkle tree + int file_first_piece_node(file_index_t index) const; + int file_first_block_node(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; + + // internal + // 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. + aux::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; + file_index_t file_index_at_piece(piece_index_t piece) const; + + // finds the file with the given root hash and returns its index + // if there is no file with the root hash, file_index_t{-1} is returned + file_index_t file_index_for_root(sha256_hash const& root_hash) const; + + // returns the piece index the given file starts at + piece_index_t piece_index_at_file(file_index_t f) const; + +#if TORRENT_USE_INVARIANT_CHECKS + // internal + bool owns_name(file_index_t const f) const + { return m_files[f].name_len == aux::file_entry::name_is_owned; } +#endif + +#if TORRENT_ABI_VERSION <= 2 + // 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``. + TORRENT_DEPRECATED + char const* file_name_ptr(file_index_t index) const; + TORRENT_DEPRECATED + int file_name_len(file_index_t index) const; +#endif + +#if TORRENT_ABI_VERSION == 1 + // these were deprecated in 1.0. Use the versions that take an index instead + TORRENT_DEPRECATED + sha1_hash hash(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::string symlink(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::time_t mtime(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + int file_index(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::string file_path(aux::file_entry const& fe, std::string const& save_path = "") const; + TORRENT_DEPRECATED + std::string file_name(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::int64_t file_size(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + bool pad_file_at(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::int64_t file_offset(aux::file_entry const& fe) const; +#endif + + // 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(); + + // returns true if this torrent contains v2 metadata. + bool v2() const { return m_v2; } + + // internal + // this is an optimization for create_torrent + std::string const& internal_symlink(file_index_t index) const; + + // internal + void remove_tail_padding(); + + private: + + std::string internal_file_path(file_index_t index) const; + file_index_t last_file() const noexcept; + + aux::path_index_t get_or_add_path(string_view path); + + // the number of bytes in a regular piece + // (i.e. not the potentially truncated last piece) + int m_piece_length = 0; + + // the number of pieces in the torrent + int m_num_pieces = 0; + + // whether this is a v2 torrent or not. Additional requirements apply to + // v2 torrents + bool m_v2 = false; + + void update_path_index(aux::file_entry& e, std::string const& path + , bool set_name = true); + + // 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 aux::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 aux::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 = 0; + }; + +namespace aux { + + TORRENT_EXTRA_EXPORT + int calc_num_pieces(file_storage const& fs); + + // this is used when loading v2 torrents that are backwards compatible with + // v1 torrents. Both v1 and v2 structures must describe the same file layout, + // this compares the two. + TORRENT_EXTRA_EXPORT + bool files_compatible(file_storage const& lhs, file_storage const& rhs); + + // 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); + + TORRENT_EXTRA_EXPORT + std::int64_t size_on_disk(file_storage const& fs); + +} // 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..cadde82 --- /dev/null +++ b/include/libtorrent/fingerprint.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2003, 2006, 2009, 2013, 2016-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_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..27e9ef9 --- /dev/null +++ b/include/libtorrent/flags.hpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2017-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_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; + +#if TORRENT_USE_IOSTREAM + friend std::ostream& operator<<(std::ostream& os, bitfield_flag val) + { return os << static_cast(val); } +#endif + +private: + UnderlyingType m_val; +}; + +} // flags +} // libtorrent + +#endif diff --git a/include/libtorrent/fwd.hpp b/include/libtorrent/fwd.hpp new file mode 100644 index 0000000..90eb65d --- /dev/null +++ b/include/libtorrent/fwd.hpp @@ -0,0 +1,319 @@ +/* + +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2017-2021, Arvid Norberg +Copyright (c) 2020, 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_FWD_HPP +#define TORRENT_FWD_HPP + +#include "libtorrent/config.hpp" + +namespace libtorrent { + +// include/libtorrent/add_torrent_params.hpp +TORRENT_VERSION_NAMESPACE_3 +struct add_torrent_params; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/alert.hpp +struct alert; + +// include/libtorrent/alert_types.hpp +struct dht_routing_bucket; +TORRENT_VERSION_NAMESPACE_3 +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 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; +struct file_prio_alert; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/announce_entry.hpp +TORRENT_VERSION_NAMESPACE_2 +struct announce_infohash; +struct announce_endpoint; +struct announce_entry; +TORRENT_VERSION_NAMESPACE_2_END + +// include/libtorrent/bdecode.hpp +struct bdecode_node; + +// include/libtorrent/bitfield.hpp +struct bitfield; + +// include/libtorrent/client_data.hpp +struct client_data_t; + +// include/libtorrent/create_torrent.hpp +struct create_torrent; + +// include/libtorrent/disk_buffer_holder.hpp +struct buffer_allocator_interface; +struct disk_buffer_holder; + +// include/libtorrent/disk_interface.hpp +struct open_file_state; +struct disk_interface; +struct storage_holder; + +// include/libtorrent/disk_observer.hpp +struct disk_observer; + +// include/libtorrent/entry.hpp +class entry; + +// include/libtorrent/error_code.hpp +struct storage_error; + +// include/libtorrent/extensions.hpp +TORRENT_VERSION_NAMESPACE_3 +struct plugin; +TORRENT_VERSION_NAMESPACE_3_END +struct torrent_plugin; +struct peer_plugin; +struct crypto_plugin; + +// include/libtorrent/file_storage.hpp +struct file_slice; +class file_storage; + +// include/libtorrent/hasher.hpp +TORRENT_CRYPTO_NAMESPACE +class hasher; +class hasher256; +TORRENT_CRYPTO_NAMESPACE_END + +// include/libtorrent/info_hash.hpp +struct info_hash_t; + +// include/libtorrent/ip_filter.hpp +struct ip_filter; +class port_filter; + +// 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/performance_counters.hpp +struct counters; + +// include/libtorrent/piece_block.hpp +struct piece_block; + +// include/libtorrent/session.hpp +struct session_proxy; +struct session; + +// include/libtorrent/session_handle.hpp +struct session_handle; + +// include/libtorrent/session_params.hpp +TORRENT_VERSION_NAMESPACE_3 +struct session_params; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/session_stats.hpp +struct stats_metric; + +// include/libtorrent/settings_pack.hpp +struct settings_interface; +struct settings_pack; + +// include/libtorrent/storage_defs.hpp +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; +struct load_torrent_limits; +TORRENT_VERSION_NAMESPACE_3 +class torrent_info; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/torrent_status.hpp +TORRENT_VERSION_NAMESPACE_3 +struct torrent_status; +TORRENT_VERSION_NAMESPACE_3_END + +#if TORRENT_ABI_VERSION <= 2 + +// include/libtorrent/alert_types.hpp +TORRENT_VERSION_NAMESPACE_3 +struct torrent_added_alert; +struct stats_alert; +struct anonymous_mode_alert; +struct mmap_cache_alert; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/file_storage.hpp +struct file_entry; + +// include/libtorrent/fingerprint.hpp +struct fingerprint; + +// include/libtorrent/kademlia/dht_settings.hpp +namespace dht { +struct dht_settings; +} + +// include/libtorrent/session_settings.hpp +struct pe_settings; + +// include/libtorrent/session_status.hpp +struct utp_status; +struct session_status; + +#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..f8f96c1 --- /dev/null +++ b/include/libtorrent/gzip.hpp @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2008-2009, 2014-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_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& ec); + + // 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 gzip_errors + +} // namespace libtorrent + +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/hash_picker.hpp b/include/libtorrent/hash_picker.hpp new file mode 100644 index 0000000..049d46f --- /dev/null +++ b/include/libtorrent/hash_picker.hpp @@ -0,0 +1,234 @@ +/* + +Copyright (c) 2017, BitTorrent Inc. +Copyright (c) 2019, Steven Siloti +Copyright (c) 2019-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_HASH_PICKER_HPP_INCLUDED +#define TORRENT_HASH_PICKER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/merkle_tree.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/time.hpp" +#include +#include + +namespace libtorrent +{ + struct torrent_peer; + + struct set_block_hash_result + { + enum class result + { + // hash is verified + success, + // hash cannot be verified yet + unknown, + // hash conflict in leaf node + block_hash_failed, + // hash conflict in a parent node + piece_hash_failed + }; + + explicit set_block_hash_result(result s) : status(s), first_verified_block(0), num_verified(0) {} + set_block_hash_result(int first_block, int num) : status(result::success), first_verified_block(first_block), num_verified(num) {} + + static set_block_hash_result unknown() { return set_block_hash_result(result::unknown); } + static set_block_hash_result block_hash_failed() { return set_block_hash_result(result::block_hash_failed); } + static set_block_hash_result piece_hash_failed() { return set_block_hash_result(result::piece_hash_failed); } + + result status; + // if status is success, this will hold the index of the first verified + // block hash as an offset from the index of the first block in the piece + int first_verified_block; + int num_verified; + }; + + struct add_hashes_result + { + explicit add_hashes_result(bool const v) : valid(v) {} + + bool valid; + // the vector contains the block indices (within the piece) that failed + // the hash check + std::vector>> hash_failed; + std::vector hash_passed; + }; + + struct node_index + { + node_index(file_index_t f, std::int32_t n) : file(f), node(n) {} + bool operator==(node_index const& o) const { return file == o.file && node == o.node; } + file_index_t file; + std::int32_t node; + }; + + // the hash request represents a range of hashes in the merkle hash tree for + // a specific file ('file'). + struct TORRENT_EXTRA_EXPORT hash_request + { + hash_request() = default; + hash_request(file_index_t const f, int const b, int const i, int const c, int const p) + : file(f), base(b), index(i), count(c), proof_layers(p) + {} + + hash_request(hash_request const&) = default; + hash_request& operator=(hash_request const& o) = default; + + bool operator==(hash_request const& o) const + { + return file == o.file && base == o.base && index == o.index && count == o.count + && proof_layers == o.proof_layers; + } + + file_index_t file{0}; + // indicates which *level* of the tree we're referring to. 0 means the + // leaf level. + int base = 0; + // the index of the first hash at the specified level. + int index = 0; + // the number of hashes in the range + int count = 0; + int proof_layers = 0; + }; + + // validates the hash_request, to ensure its invariant as well as matching + // the torrent's file_storage and the number of hashes accompanying the + // request + TORRENT_EXTRA_EXPORT + bool validate_hash_request(hash_request const& hr, file_storage const& fs); + + class TORRENT_EXTRA_EXPORT hash_picker + { + public: + hash_picker(file_storage const& files + , aux::vector& trees); + + hash_request pick_hashes(typed_bitfield const& pieces); + + add_hashes_result add_hashes(hash_request const& req, span hashes); + // TODO: support batched adding of block hashes for reduced overhead? + set_block_hash_result set_block_hash(piece_index_t piece, int offset, sha256_hash const& h); + void hashes_rejected(hash_request const& req); + void verify_block_hashes(piece_index_t index); + + // do we know the piece layer hash for a piece + bool have_hash(piece_index_t index) const; + // do we know all the block hashes for a file? + bool have_all(file_index_t file) const; + bool have_all() const; + bool piece_verified(piece_index_t piece) const; + + int piece_layer() const { return m_piece_layer; } + + private: + // returns the number of proof layers needed to verify the node's hash + int layers_to_verify(node_index idx) const; + int file_num_layers(file_index_t idx) const; + + struct piece_hash_request + { + time_point last_request = min_time(); + int num_requests = 0; + bool have = false; + }; + + struct priority_block_request + { + priority_block_request(file_index_t const f, int const b) + : file(f), block(b) {} + file_index_t file; + int block; + int num_requests = 0; + bool operator==(priority_block_request const& o) const + { return file == o.file && block == o.block; } + bool operator!=(priority_block_request const& o) const + { return !(*this == o); } + bool operator<(priority_block_request const& o) const + { return num_requests < o.num_requests; } + }; + + struct piece_block_request + { + piece_block_request(file_index_t const f, piece_index_t::diff_type const p) : file(f), piece(p) {} + file_index_t file; + // the piece from the start of the file + piece_index_t::diff_type piece; + time_point last_request = min_time(); + int num_requests = 0; + bool operator==(piece_block_request const& o) const + { return file == o.file && piece == o.piece; } + bool operator!=(piece_block_request const& o) const + { return !(*this == o); } + bool operator<(piece_block_request const& o) const + { return num_requests < o.num_requests; } + }; + + file_storage const& m_files; + aux::vector& m_merkle_trees; + + // information about every 512-piece span of each file. We request hashes + // for 512 pieces at a time + aux::vector, file_index_t> m_piece_hash_requested; + + // this is for a future per-block request feature +#if 0 + // blocks are only added to this list if there is a time critial block which + // has been downloaded but we don't have its hash or if the initial request + // for the hash was rejected + // this block hash will be requested from every peer possible until the hash + // is received + // the vector is sorted by the number of requests sent for each block + aux::vector m_priority_block_requests; +#endif + + // when a piece fails hash check a request is queued to download the piece's + // block hashes + aux::vector m_piece_block_requests; + + // this is the number of tree levels in a piece. if the piece size is 16 + // kiB, this is 0, since there is no tree per piece. If the piece size is + // 32 kiB, it's 1, and so on. + int const m_piece_layer; + + // this is the number of tree layers for a 512-piece range, which is + // the granularity with which we send hash requests. The number of layers + // all the way down the the block level. + int const m_piece_tree_root_layer; + }; +} // namespace libtorrent + +#endif // TORRENT_HASH_PICKER_HPP_INCLUDED diff --git a/include/libtorrent/hasher.hpp b/include/libtorrent/hasher.hpp new file mode 100644 index 0000000..cf38835 --- /dev/null +++ b/include/libtorrent/hasher.hpp @@ -0,0 +1,185 @@ +/* + +Copyright (c) 2003-2004, 2007, 2009, 2012-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017, 2019, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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_CNG +#include "libtorrent/aux_/win_cng.hpp" + +#elif TORRENT_USE_CRYPTOAPI +#include "libtorrent/aux_/win_crypto_provider.hpp" + +#if !TORRENT_USE_CRYPTOAPI_SHA_512 +#include "libtorrent/sha256.hpp" +#endif + +#elif defined TORRENT_USE_LIBCRYPTO + +extern "C" { +#include +} + +#else +#include "libtorrent/sha1.hpp" +#include "libtorrent/sha256.hpp" +#endif +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { +TORRENT_CRYPTO_NAMESPACE + + // 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_CNG + aux::cng_hash 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 + }; + + class TORRENT_EXPORT hasher256 + { + public: + hasher256(); + + // this is the same as default constructing followed by a call to + // ``update(data, len)``. + hasher256(char const* data, int len); + explicit hasher256(span data); + hasher256(hasher256 const&); + hasher256& operator=(hasher256 const&) &; + + // append the following bytes to what is being hashed + hasher256& update(span data); + hasher256& update(char const* data, int len); + + // returns the SHA-1 digest of the buffers previously passed to + // update() and the hasher constructor. + sha256_hash final(); + + // restore the hasher state to be as if the hasher has just been + // default constructed. + void reset(); + + ~hasher256(); + + private: +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_hd_t m_context; +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA256_CTX m_context; +#elif TORRENT_USE_CNG + aux::cng_hash m_context; +#elif TORRENT_USE_CRYPTOAPI_SHA_512 + aux::crypt_hash m_context; +#elif defined TORRENT_USE_LIBCRYPTO + SHA256_CTX m_context; +#else + sha256_ctx m_context; +#endif + }; + +TORRENT_CRYPTO_NAMESPACE_END +} + +#endif // TORRENT_HASHER_HPP_INCLUDED diff --git a/include/libtorrent/hex.hpp b/include/libtorrent/hex.hpp new file mode 100644 index 0000000..140f944 --- /dev/null +++ b/include/libtorrent/hex.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2004, 2009, 2013, 2015-2020, Arvid Norberg +Copyright (c) 2020, 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_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 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 + +} // namespace aux + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // 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); } + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif +} // namespace libtorrent + +#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..745d5ed --- /dev/null +++ b/include/libtorrent/http_connection.hpp @@ -0,0 +1,257 @@ +/* + +Copyright (c) 2007-2020, Arvid Norberg +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016-2017, 2020, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 + +#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/aux_/resolver_interface.hpp" +#include "libtorrent/optional.hpp" +#include "libtorrent/ssl.hpp" + +namespace libtorrent { + +struct http_connection; +namespace aux { struct resolver_interface; } + +struct close_visitor; + +// 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&)>; +using hostname_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 +{ + friend struct close_visitor; + + http_connection(io_context& ios + , aux::resolver_interface& resolver + , http_handler handler + , bool bottled + , int max_bottled_buffer_size + , http_connect_handler ch + , http_filter_handler fh + , hostname_filter_handler hfh +#if TORRENT_USE_SSL + , 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
    () + , aux::resolver_flags resolve_flags = aux::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
    () + , aux::resolver_flags resolve_flags = aux::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; } + + std::string const& url() const { return m_url; } + +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; + io_context& m_ios; + + 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; + + boost::optional m_sock; + +#if TORRENT_USE_SSL + ssl::context* m_ssl_ctx; +#endif + +#if TORRENT_USE_I2P + i2p_connection* m_i2p_conn; +#endif + aux::resolver_interface& m_resolver; + + http_parser m_parser; + http_handler m_handler; + http_connect_handler m_connect_handler; + http_filter_handler m_filter_handler; + hostname_filter_handler m_hostname_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 + aux::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..f26faa5 --- /dev/null +++ b/include/libtorrent/http_parser.hpp @@ -0,0 +1,168 @@ +/* + +Copyright (c) 2008-2017, 2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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" +#include "libtorrent/aux_/strview_less.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..55ab891 --- /dev/null +++ b/include/libtorrent/http_seed_connection.hpp @@ -0,0 +1,114 @@ +/* + +Copyright (c) 2008-2010, 2012, 2014-2020, Arvid Norberg +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_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 { + + struct torrent; + struct peer_request; + + class TORRENT_EXTRA_EXPORT http_seed_connection + : public web_connection_base + { + friend struct 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& 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..97bbafd --- /dev/null +++ b/include/libtorrent/http_stream.hpp @@ -0,0 +1,230 @@ +/* + +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2007, 2010, 2015, 2019-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_HTTP_STREAM_HPP_INCLUDED +#define TORRENT_HTTP_STREAM_HPP_INCLUDED + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for base64encode +#include "libtorrent/socket_io.hpp" // for print_endpoint + +namespace libtorrent { + +class http_stream : public proxy_base +{ +public: + + explicit http_stream(io_context& io_context) + : proxy_base(io_context) + , 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 + + m_resolver.async_resolve(m_hostname, to_string(m_port).data(), wrap_allocator( + [this](error_code const& ec, tcp::resolver::results_type ips, Handler hn) { + name_lookup(ec, std::move(ips), std::move(hn)); + }, std::move(handler))); + } + +private: + + template + void name_lookup(error_code const& e, tcp::resolver::results_type ips + , Handler h) + { + if (handle_error(e, h)) return; + + auto i = ips.begin(); + m_sock.async_connect(i->endpoint(), wrap_allocator( + [this](error_code const& ec, Handler hn) { + connected(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connected(error_code const& e, Handler h) + { + if (handle_error(e, h)) return; + + using namespace libtorrent::aux; + + if (m_no_connect) + { + std::vector().swap(m_buffer); + std::move(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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake1(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake1(error_code const& e, Handler 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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake2(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake2(error_code const& e, Handler 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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake2(ec, std::move(hn)); + }, std::move(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..0c775af --- /dev/null +++ b/include/libtorrent/http_tracker_connection.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2004, 2006-2009, 2012, 2014-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, 2018, 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_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_context& ios + , tracker_manager& man + , tracker_request 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); + bool on_filter_hostname(http_connection& c, string_view hostname); + 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; + io_context& m_ioc; + }; + + TORRENT_EXTRA_EXPORT tracker_response parse_tracker_response( + span data, error_code& ec + , tracker_request_flags_t 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..2f201eb --- /dev/null +++ b/include/libtorrent/i2p_stream.hpp @@ -0,0 +1,624 @@ +/* + +Copyright (c) 2009, 2013-2019, Arvid Norberg +Copyright (c) 2016-2017, 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_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" +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/hex.hpp" // for to_hex +#include "libtorrent/debug.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); +} +} + +namespace boost { +namespace system { + +template<> +struct is_error_code_enum +{ static const bool value = true; }; + +} +} + + +namespace libtorrent { + + // 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 + +struct i2p_stream : proxy_base +{ + explicit i2p_stream(io_context& io_context); + i2p_stream(i2p_stream&&) noexcept = default; +#if TORRENT_USE_ASSERTS + ~i2p_stream(); +#endif + // explicitly disallow assignment, to silence msvc warning + i2p_stream& operator=(i2p_stream const&) = delete; + + enum command_t : std::uint8_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 h) + { + // 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) + + m_resolver.async_resolve(m_hostname, to_string(m_port).data(), wrap_allocator( + [this](error_code const& ec, tcp::resolver::results_type ips, Handler hn) { + do_connect(ec, std::move(ips), std::move(hn)); + }, std::move(h))); + } + + std::string name_lookup() const { return m_name_lookup; } + void set_name_lookup(char const* name) { m_name_lookup = name; } + + template + void send_name_lookup(Handler 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)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + +private: + + template + void do_connect(error_code const& e, tcp::resolver::results_type ips, Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + if (e || ips.empty()) + { + h(e); + error_code ec; + close(ec); + return; + } + + auto i = ips.begin(); + ADD_OUTSTANDING_ASYNC("i2p_stream::connected"); + m_sock.async_connect(i->endpoint(), wrap_allocator( + [this](error_code const& ec, Handler hn) { + connected(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connected(error_code const& e, Handler 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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void start_read_line(error_code const& e, Handler 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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void read_line(error_code const& e, Handler 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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + + async_read(m_sock, boost::asio::buffer(&m_buffer[read_pos], 1), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + read_line(ec, std::move(hn)); + }, 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), wrap_allocator( + [this](error_code const& err, std::size_t, Handler hn) { + read_line(err, std::move(hn)); + }, std::move(h))); + break; + } + } + + template + void send_connect(Handler 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)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void send_accept(Handler 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)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void send_session_create(Handler 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)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + + // send and receive buffer + aux::noexcept_movable> m_buffer; + char const* m_id; + std::string m_dest; + std::string m_name_lookup; + + enum state_t : std::uint8_t + { + read_hello_response, + read_connect_response, + read_accept_response, + read_session_create_response, + read_name_lookup_response + }; + + command_t m_command; + state_t m_state; +#if TORRENT_USE_ASSERTS + int m_magic; +#endif +}; + +class i2p_connection +{ +public: + explicit i2p_connection(io_context& ios); + ~i2p_connection(); + // explicitly disallow assignment, to silence msvc warning + i2p_connection& operator=(i2p_connection const&) = delete; + + aux::proxy_settings proxy() const; + + bool is_open() const + { + return m_sam_socket + && m_sam_socket->is_open() + && m_state != sam_connecting; + } + template + void open(std::string const& hostname, int port, Handler handler) + { + // we already seem to have a session to this SAM router + if (m_hostname == hostname + && m_port == port + && m_sam_socket + && (is_open() || m_state == sam_connecting)) return; + + m_hostname = hostname; + 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 = std::make_shared(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(), wrap_allocator( + [this,s=m_sam_socket](error_code const& ec, Handler hn) { + on_sam_connect(ec, s, std::move(hn)); + }, std::move(handler))); + } + 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; } + + template + void async_name_lookup(char const* name, 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)); + } + +private: + + template + void on_sam_connect(error_code const& ec, std::shared_ptr, Handler h) + { + COMPLETE_ASYNC("i2p_stream::on_sam_connect"); + m_state = sam_idle; + + if (ec) + { + h(ec); + return; + } + + do_name_lookup("ME", wrap_allocator( + [this](error_code const& e, char const* dst, Handler hn) { + set_local_endpoint(e, dst, std::move(hn)); + }, std::move(h))); + } + + using name_lookup_handler = std::function; + + template + void do_name_lookup(std::string const& name, 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(wrap_allocator( + [this,s=m_sam_socket](error_code const& ec, Handler hn) { + on_name_lookup(ec, s, std::move(hn)); + }, std::move(handler))); + } + + template + void on_name_lookup(error_code const& ec, std::shared_ptr, Handler handler) + { + 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()); + } + + + template + void set_local_endpoint(error_code const& ec, char const* dest, Handler h) + { + if (!ec && dest != nullptr) + m_i2p_local_endpoint = dest; + else + m_i2p_local_endpoint.clear(); + + h(ec); + } + + // 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_context& m_io_service; +}; + +} + +#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..64bf9b2 --- /dev/null +++ b/include/libtorrent/identify_client.hpp @@ -0,0 +1,86 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2003, 2006, 2013-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_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 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // 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); + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#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..ad5e292 --- /dev/null +++ b/include/libtorrent/index_range.hpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2018-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_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() const { return index_iter{_begin}; } + index_iter end() const { return index_iter{_end}; } +}; + +} + +#endif diff --git a/include/libtorrent/info_hash.hpp b/include/libtorrent/info_hash.hpp new file mode 100644 index 0000000..c93f53f --- /dev/null +++ b/include/libtorrent/info_hash.hpp @@ -0,0 +1,166 @@ +/* + +Copyright (c) 2018, BitTorrent Inc. +Copyright (c) 2018, Steven Siloti +Copyright (c) 2019-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_INFO_HASH_HPP_INCLUDED +#define TORRENT_INFO_HASH_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/sha1_hash.hpp" + +#if TORRENT_USE_IOSTREAM +#include +#endif // TORRENT_USE_IOSTREAM + +namespace libtorrent +{ + // BitTorrent version enumerator + enum class protocol_version : std::uint8_t + { + // The original BitTorrent version, using SHA-1 hashes + V1, + // Version 2 of the BitTorrent protocol, using SHA-256 hashes + V2, + NUM + }; + + // internal + constexpr std::size_t num_protocols = int(protocol_version::NUM); + +namespace { + std::initializer_list const all_versions{ + protocol_version::V1, + protocol_version::V2 + }; +} + + // class holding the info-hash of a torrent. It can hold a v1 info-hash + // (SHA-1) or a v2 info-hash (SHA-256) or both. + // + // .. note:: + // + // If ``has_v2()`` is false then the v1 hash might actually be a truncated + // v2 hash + struct TORRENT_EXPORT info_hash_t + { + // The default constructor creates an object that has neither a v1 or v2 + // hash. + // + // For backwards compatibility, make it possible to construct directly + // from a v1 hash. This constructor allows *implicit* conversion from a + // v1 hash, but the implicitness is deprecated. + info_hash_t() noexcept = default; + explicit info_hash_t(sha1_hash h1) noexcept : v1(h1) {} // NOLINT + explicit info_hash_t(sha256_hash h2) noexcept : v2(h2) {} + info_hash_t(sha1_hash h1, sha256_hash h2) noexcept + : v1(h1), v2(h2) {} + + // hidden + info_hash_t(info_hash_t const&) noexcept = default; + info_hash_t& operator=(info_hash_t const&) & noexcept = default; + + // returns true if the corresponding info hash is present in this + // object. + bool has_v1() const { return !v1.is_all_zeros(); } + bool has_v2() const { return !v2.is_all_zeros(); } + bool has(protocol_version v) const + { + TORRENT_ASSERT(v != protocol_version::NUM); + return v == protocol_version::V1 ? has_v1() : has_v2(); + } + + // returns the has for the specified protocol version + sha1_hash get(protocol_version v) const + { + TORRENT_ASSERT(v != protocol_version::NUM); + return v == protocol_version::V1 ? v1 : sha1_hash(v2.data()); + } + + // returns the v2 (truncated) info-hash, if there is one, otherwise + // returns the v1 info-hash + sha1_hash get_best() const + { + return has_v2() ? get(protocol_version::V2) : v1; + } + + friend bool operator!=(info_hash_t const& lhs, info_hash_t const& rhs) + { + return std::tie(lhs.v1, lhs.v2) != std::tie(rhs.v1, rhs.v2); + } + + friend bool operator==(info_hash_t const& lhs, info_hash_t const& rhs) noexcept + { + return std::tie(lhs.v1, lhs.v2) == std::tie(rhs.v1, rhs.v2); + } + + // calls the function object ``f`` for each hash that is available. + // starting with v1. The signature of ``F`` is:: + // + // void(sha1_hash, protocol_version); + template void for_each(F f) const + { + if (has_v1()) f(v1, protocol_version::V1); + if (has_v2()) f(sha1_hash(v2.data()), protocol_version::V2); + } + + bool operator<(info_hash_t const& o) const + { + return std::tie(v1, v2) < std::tie(o.v1, o.v2); + } + +#if TORRENT_USE_IOSTREAM + friend std::ostream& operator<<(std::ostream& os, info_hash_t const& ih) + { + return os << '[' << ih.v1 << ',' << ih.v2 << ']'; + } +#endif // TORRENT_USE_IOSTREAM + + sha1_hash v1; + sha256_hash v2; + }; + +} + +namespace std { + template <> + struct hash + { + std::size_t operator()(libtorrent::info_hash_t const& k) const + { + return std::hash{}(k.v1) + ^ std::hash{}(k.v2) ; + } + }; +} + +#endif diff --git a/include/libtorrent/io.hpp b/include/libtorrent/io.hpp new file mode 100644 index 0000000..29b3676 --- /dev/null +++ b/include/libtorrent/io.hpp @@ -0,0 +1,189 @@ +/* + +Copyright (c) 2004, 2007, 2009, 2011, 2016-2017, 2019, Arvid Norberg +Copyright (c) 2016-2017, 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_IO_HPP_INCLUDED +#define TORRENT_IO_HPP_INCLUDED + +#include +#include +#include // for copy +#include // for memcpy +#include +#include + +#include "assert.hpp" +#include "libtorrent/aux_/io.hpp" + +namespace libtorrent { +namespace aux { + + // 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 aux +} + +#endif // TORRENT_IO_HPP_INCLUDED diff --git a/include/libtorrent/io_context.hpp b/include/libtorrent/io_context.hpp new file mode 100644 index 0000000..efb2710 --- /dev/null +++ b/include/libtorrent/io_context.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2006-2007, 2009, 2015, 2017, 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_IO_CONTEXT_HPP_INCLUDED +#define TORRENT_IO_CONTEXT_HPP_INCLUDED + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#else +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // SIMULATOR + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::io_context; +#else + using boost::asio::io_context; +#endif + using boost::asio::executor_work_guard; + using boost::asio::make_work_guard; + + using boost::asio::post; + using boost::asio::dispatch; + using boost::asio::defer; +} + +#endif diff --git a/include/libtorrent/io_service.hpp b/include/libtorrent/io_service.hpp new file mode 100644 index 0000000..86b0b99 --- /dev/null +++ b/include/libtorrent/io_service.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2006-2007, 2009, 2015, 2017, 2019, Arvid Norberg +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_IO_SERVICE_HPP_INCLUDED +#define TORRENT_IO_SERVICE_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#error warning "this header is deprecated, use io_context.hpp instead" +namespace libtorrent { + + using io_service = boost::asio::io_context; +} + +#endif diff --git a/include/libtorrent/ip_filter.hpp b/include/libtorrent/ip_filter.hpp new file mode 100644 index 0000000..fdaccc0 --- /dev/null +++ b/include/libtorrent/ip_filter.hpp @@ -0,0 +1,241 @@ +/* + +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2005-2007, 2009-2010, 2013, 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016-2018, 2020, 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_IP_FILTER_HPP +#define TORRENT_IP_FILTER_HPP + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include +#include + +#include "libtorrent/address.hpp" + +namespace libtorrent { + +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 aux { + + template + TORRENT_EXTRA_EXPORT Addr zero(); + template + TORRENT_EXTRA_EXPORT Addr plus_one(Addr const& a); + template + TORRENT_EXTRA_EXPORT Addr minus_one(Addr const& a); + template + TORRENT_EXTRA_EXPORT Addr max_addr(); + + extern template address_v4::bytes_type minus_one(address_v4::bytes_type const&); + extern template address_v6::bytes_type minus_one(address_v6::bytes_type const&); + extern template address_v4::bytes_type plus_one(address_v4::bytes_type const&); + extern template address_v6::bytes_type plus_one(address_v6::bytes_type const&); + extern template address_v4::bytes_type zero(); + extern template address_v6::bytes_type zero(); + extern template address_v4::bytes_type max_addr(); + extern template address_v6::bytes_type max_addr(); + + inline std::uint16_t plus_one(std::uint16_t val) { return val + 1; } + inline std::uint16_t minus_one(std::uint16_t val) { return val - 1; } + template<> + inline std::uint16_t zero() { return 0; } + 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(); + bool empty() const; + void add_rule(Addr first, Addr last, std::uint32_t flags); + std::uint32_t access(Addr const& addr) const; + template + std::vector> export_filter() const; + + 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; + friend bool operator==(range const& lhs, range const& rhs) + { return lhs.start == rhs.start && lhs.access == rhs.access; } + }; + + std::set m_access_list; + }; + + extern template class filter_impl; + extern template class filter_impl; + extern template class filter_impl; + + extern template std::vector> filter_impl::export_filter() const; + extern template std::vector> filter_impl::export_filter() const; + extern template std::vector> filter_impl::export_filter() const; +} + +// 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 +{ + ip_filter(); + ip_filter(ip_filter const&); + ip_filter(ip_filter&&); + ip_filter& operator=(ip_filter const&); + ip_filter& operator=(ip_filter&&); + ~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 + }; + + // returns true if the filter does not contain any rules + bool empty() const; + + // 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; + +private: + + aux::filter_impl m_filter4; + aux::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: + + port_filter(); + port_filter(port_filter const&); + port_filter(port_filter&&); + port_filter& operator=(port_filter const&); + port_filter& operator=(port_filter&&); + ~port_filter(); + + // 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: + + aux::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..e0e2846 --- /dev/null +++ b/include/libtorrent/ip_voter.hpp @@ -0,0 +1,136 @@ +/* + +Copyright (c) 2013-2015, 2017, 2019, Arvid Norberg +Copyright (c) 2016, Steven Siloti +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_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..6d79345 --- /dev/null +++ b/include/libtorrent/kademlia/announce_flags.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2018-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 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..3b49ab9 --- /dev/null +++ b/include/libtorrent/kademlia/dht_observer.hpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2012, 2014-2017, 2019, Arvid Norberg +Copyright (c) 2014, 2017-2018, Steven Siloti +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 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..ca2409a --- /dev/null +++ b/include/libtorrent/kademlia/dht_settings.hpp @@ -0,0 +1,184 @@ +/* + +Copyright (c) 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 { + +#if TORRENT_ABI_VERSION <= 2 + // this is deprecated. Use settings_pack and apply_settings on the session + // instead. + + // structure used to hold configuration options for the DHT + struct TORRENT_DEPRECATED_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; + }; + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // 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); + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + +} +} + +#endif diff --git a/include/libtorrent/kademlia/dht_state.hpp b/include/libtorrent/kademlia/dht_state.hpp new file mode 100644 index 0000000..15506e2 --- /dev/null +++ b/include/libtorrent/kademlia/dht_state.hpp @@ -0,0 +1,81 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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..b80b6ed --- /dev/null +++ b/include/libtorrent/kademlia/dht_storage.hpp @@ -0,0 +1,245 @@ +/* + +Copyright (c) 2015-2019, Arvid Norberg +Copyright (c) 2015-2017, Alden Torres +Copyright (c) 2016, Steven Siloti +Copyright (c) 2019, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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; + struct settings_interface; +} + +namespace libtorrent { +namespace dht { + + // 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 settings_pack::dht_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 + // settings_pack::dht_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 + // settings_pack::dht_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(settings_interface 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( + settings_interface const& settings); + +} // namespace dht +} // namespace libtorrent + +#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..74b4dc7 --- /dev/null +++ b/include/libtorrent/kademlia/dht_tracker.hpp @@ -0,0 +1,230 @@ +/* + +Copyright (c) 2006-2008, 2010, 2014-2020, Arvid Norberg +Copyright (c) 2014-2015, 2017, Steven Siloti +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2015-2017, Alden Torres +Copyright (c) 2020, Fonic +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 aux { + struct session_settings; +} +} + +namespace libtorrent { +namespace dht { + + 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_context& ios + , send_fun_t send_fun + , aux::session_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_context& ios + , send_fun_t const& send_fun + , aux::session_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 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + void dht_status(session_status& s); +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + std::vector dht_status() const; + 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_context& ios + , aux::listen_socket_handle const& s, socket_manager* sock + , aux::session_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; + aux::session_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; + + io_context& m_ioc; + }; +} // namespace dht +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/kademlia/direct_request.hpp b/include/libtorrent/kademlia/direct_request.hpp new file mode 100644 index 0000000..774311e --- /dev/null +++ b/include/libtorrent/kademlia/direct_request.hpp @@ -0,0 +1,98 @@ +/* + +Copyright (c) 2014-2015, Steven Siloti +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2015-2016, 2018, Alden Torres +Copyright (c) 2016-2017, 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_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 dht +} // namespace libtorrent + +#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..4d81acf --- /dev/null +++ b/include/libtorrent/kademlia/dos_blocker.hpp @@ -0,0 +1,94 @@ +/* + +Copyright (c) 2010, 2013-2017, 2019-2020, Arvid Norberg +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_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) + { + m_message_rate_limit = std::max(1, l); + } + + void set_block_timer(int t) + { + m_block_timeout = std::max(1, 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..c752459 --- /dev/null +++ b/include/libtorrent/kademlia/ed25519.hpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2016, Alden Torres +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 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..72cbf54 --- /dev/null +++ b/include/libtorrent/kademlia/find_data.hpp @@ -0,0 +1,91 @@ +/* + +Copyright (c) 2006, Daniel Wallin +Copyright (c) 2006-2010, 2013-2019, Arvid Norberg +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 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 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 dht +} // namespace libtorrent + +#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..b31b34a --- /dev/null +++ b/include/libtorrent/kademlia/get_item.hpp @@ -0,0 +1,98 @@ +/* + +Copyright (c) 2013, Steven Siloti +Copyright (c) 2013-2019, Arvid Norberg +Copyright (c) 2015, Thomas Yuan +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 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 dcallback + , nodes_callback ncallback); + + // for mutable items + get_item(node& dht_node + , public_key const& pk + , span salt + , data_callback dcallback + , nodes_callback 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 dht +} // namespace libtorrent + +#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..48c749a --- /dev/null +++ b/include/libtorrent/kademlia/get_peers.hpp @@ -0,0 +1,115 @@ +/* + +Copyright (c) 2006, Daniel Wallin +Copyright (c) 2013, 2017-2019, Arvid Norberg +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2016, 2018, 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_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 dcallback + , nodes_callback 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 dcallback + , nodes_callback 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 dht +} // namespace libtorrent + +#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..f3e7a93 --- /dev/null +++ b/include/libtorrent/kademlia/io.hpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2016, Steven Siloti +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 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 = aux::read_v6_endpoint(in); + else + ep.ep = aux::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..d440009 --- /dev/null +++ b/include/libtorrent/kademlia/item.hpp @@ -0,0 +1,130 @@ +/* + +Copyright (c) 2013-2019, Steven Siloti +Copyright (c) 2013-2016, 2018-2019, Arvid Norberg +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 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 dht +} // namespace libtorrent + +#endif // LIBTORRENT_ITEM_HPP diff --git a/include/libtorrent/kademlia/msg.hpp b/include/libtorrent/kademlia/msg.hpp new file mode 100644 index 0000000..30220fe --- /dev/null +++ b/include/libtorrent/kademlia/msg.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2007, 2009, 2015, 2019, Arvid Norberg +Copyright (c) 2015, Steven Siloti +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_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) {} + + // explicitly disallow assignment, to silence msvc warning + msg& operator=(msg const&) = delete; + + // the message + bdecode_node const& message; + + // the address of the process sending or receiving + // the message. + udp::endpoint addr; +}; + +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& message, 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..826f2a7 --- /dev/null +++ b/include/libtorrent/kademlia/node.hpp @@ -0,0 +1,305 @@ +/* + +Copyright (c) 2006-2020, Arvid Norberg +Copyright (c) 2014-2017, Steven Siloti +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2015-2017, Alden Torres +Copyright (c) 2020, Fonic +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 // for udp::endpoint +#include +#include + +// for dht_lookup and dht_routing_bucket +#include + +namespace libtorrent { + struct counters; +} + +namespace libtorrent { +namespace dht { + +struct traversal_algorithm; +struct dht_observer; +struct msg; +struct settings; + +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; + +struct dht_status +{ + node_id our_id; + udp::endpoint local_endpoint; + std::vector table; + std::vector requests; +}; + +class TORRENT_EXTRA_EXPORT node +{ +public: + node(aux::listen_socket_handle const& sock, socket_manager* sock_man + , aux::session_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; + + 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); + } + + dht_status status() const; + + std::tuple get_stats_counters() const; + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + void status(libtorrent::session_status& s); +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + + aux::session_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; + + aux::session_settings const& m_settings; + + mutable 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&, entry&); + + 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::array m_secret[2]; + + counters& m_counters; + + dht_storage_interface& m_storage; + +#ifndef TORRENT_DISABLE_LOGGING + std::uint32_t m_search_id = 0; +#endif +}; + +} // namespace dht +} // namespace libtorrent + +#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..fba9ba9 --- /dev/null +++ b/include/libtorrent/kademlia/node_entry.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2006, 2008-2009, 2013-2016, 2019, Arvid Norberg +Copyright (c) 2015, Steven Siloti +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 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 dht +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/kademlia/node_id.hpp b/include/libtorrent/kademlia/node_id.hpp new file mode 100644 index 0000000..3185df0 --- /dev/null +++ b/include/libtorrent/kademlia/node_id.hpp @@ -0,0 +1,77 @@ +/* + +Copyright (c) 2006, 2008, 2013, 2015-2016, 2019, Arvid Norberg +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 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 dht +} // namespace libtorrent + +#endif // NODE_ID_HPP diff --git a/include/libtorrent/kademlia/observer.hpp b/include/libtorrent/kademlia/observer.hpp new file mode 100644 index 0000000..a034012 --- /dev/null +++ b/include/libtorrent/kademlia/observer.hpp @@ -0,0 +1,156 @@ +/* + +Copyright (c) 2007-2010, 2013-2019, Arvid Norberg +Copyright (c) 2014, Steven Siloti +Copyright (c) 2015, Thomas Yuan +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 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..13861e3 --- /dev/null +++ b/include/libtorrent/kademlia/put_data.hpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016-2019, Arvid Norberg +Copyright (c) 2016, 2018, 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_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 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 dht +} // namespace libtorrent + +#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..6f67473 --- /dev/null +++ b/include/libtorrent/kademlia/refresh.hpp @@ -0,0 +1,67 @@ +/* + +Copyright (c) 2006, Daniel Wallin +Copyright (c) 2006, 2008, 2010, 2013-2014, 2017-2019, Arvid Norberg +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 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; + +}; + +} // namesapce dht +} // namespace libtorrent + +#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..983b6c5 --- /dev/null +++ b/include/libtorrent/kademlia/routing_table.hpp @@ -0,0 +1,345 @@ +/* + +Copyright (c) 2006-2007, 2009-2019, Arvid Norberg +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2015-2016, Steven Siloti +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2016, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 +#include + +namespace libtorrent { +namespace aux { + struct session_settings; +} +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); + +using find_nodes_flags_t = flags::bitfield_flag; + +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 + , aux::session_settings const& settings + , dht_logger* log); + + routing_table(routing_table const&) = delete; + routing_table& operator=(routing_table const&) = delete; + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + void status(session_status& s) const; +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#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(); + + // nodes that have not been pinged are considered failed by this flag + static constexpr find_nodes_flags_t include_failed = 0_bit; + + // fills the vector with the count nodes from our buckets that + // are nearest to the given id. + std::vector find_node(node_id const& target + , find_nodes_flags_t 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(); + + aux::session_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..84eee6c --- /dev/null +++ b/include/libtorrent/kademlia/rpc_manager.hpp @@ -0,0 +1,147 @@ +/* + +Copyright (c) 2006-2017, 2019-2020, Arvid Norberg +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016-2017, Steven Siloti +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 RPC_MANAGER_HPP +#define RPC_MANAGER_HPP + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace libtorrent { +class entry; +namespace aux { + struct session_settings; +} +} + +namespace libtorrent { +namespace dht { + +struct 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 + , aux::session_settings const& settings + , routing_table& table + , aux::listen_socket_handle 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 lt::aux::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 + aux::session_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 dht +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/kademlia/sample_infohashes.hpp b/include/libtorrent/kademlia/sample_infohashes.hpp new file mode 100644 index 0000000..e3848d0 --- /dev/null +++ b/include/libtorrent/kademlia/sample_infohashes.hpp @@ -0,0 +1,84 @@ +/* + +Copyright (c) 2017, 2019-2020, Arvid Norberg +Copyright (c) 2017, Alden Torres +Copyright (c) 2020, Fonic +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 dcallback); + + char const* name() const override; + + void got_samples(sha1_hash const& nid + , 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 dht +} // namespace libtorrent + +#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..653a0a8 --- /dev/null +++ b/include/libtorrent/kademlia/traversal_algorithm.hpp @@ -0,0 +1,176 @@ +/* + +Copyright (c) 2006, Daniel Wallin +Copyright (c) 2006, 2008-2010, 2013-2019, Arvid Norberg +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016, Alden Torres +Copyright (c) 2016-2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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..c98c1e4 --- /dev/null +++ b/include/libtorrent/kademlia/types.hpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2016-2017, 2019-2020, Arvid Norberg +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 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; + }; + +} // namespace dht +} // namespace libtorrent + +#endif // LIBTORRENT_TYPES_HPP diff --git a/include/libtorrent/libtorrent.hpp b/include/libtorrent/libtorrent.hpp new file mode 100644 index 0000000..756ffb2 --- /dev/null +++ b/include/libtorrent/libtorrent.hpp @@ -0,0 +1,168 @@ + +// This header is generated by tools/gen_convenience_header.py + +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/bloom_filter.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/choker.hpp" +#include "libtorrent/client_data.hpp" +#include "libtorrent/close_reason.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/copy_ptr.hpp" +#include "libtorrent/crc32c.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/disabled_disk_io.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/disk_observer.hpp" +#include "libtorrent/download_priority.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/extensions/smart_ban.hpp" +#include "libtorrent/extensions/ut_metadata.hpp" +#include "libtorrent/extensions/ut_pex.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/gzip.hpp" +#include "libtorrent/hash_picker.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/hex.hpp" +#include "libtorrent/http_connection.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/http_seed_connection.hpp" +#include "libtorrent/http_stream.hpp" +#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/i2p_stream.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/index_range.hpp" +#include "libtorrent/info_hash.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/kademlia/announce_flags.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/kademlia/dht_settings.hpp" +#include "libtorrent/kademlia/dht_state.hpp" +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/kademlia/direct_request.hpp" +#include "libtorrent/kademlia/dos_blocker.hpp" +#include "libtorrent/kademlia/ed25519.hpp" +#include "libtorrent/kademlia/find_data.hpp" +#include "libtorrent/kademlia/get_item.hpp" +#include "libtorrent/kademlia/get_peers.hpp" +#include "libtorrent/kademlia/io.hpp" +#include "libtorrent/kademlia/item.hpp" +#include "libtorrent/kademlia/msg.hpp" +#include "libtorrent/kademlia/node.hpp" +#include "libtorrent/kademlia/node_entry.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/observer.hpp" +#include "libtorrent/kademlia/put_data.hpp" +#include "libtorrent/kademlia/refresh.hpp" +#include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/kademlia/rpc_manager.hpp" +#include "libtorrent/kademlia/sample_infohashes.hpp" +#include "libtorrent/kademlia/traversal_algorithm.hpp" +#include "libtorrent/kademlia/types.hpp" +#include "libtorrent/link.hpp" +#include "libtorrent/lsd.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/mmap_disk_io.hpp" +#include "libtorrent/mmap_storage.hpp" +#include "libtorrent/natpmp.hpp" +#include "libtorrent/netlink.hpp" +#include "libtorrent/operations.hpp" +#include "libtorrent/optional.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/part_file.hpp" +#include "libtorrent/pe_crypto.hpp" +#include "libtorrent/peer.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/peer_class_type_filter.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/peer_connection_handle.hpp" +#include "libtorrent/peer_connection_interface.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/peer_list.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/pex_flags.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/platform_util.hpp" +#include "libtorrent/portmap.hpp" +#include "libtorrent/posix_disk_io.hpp" +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/puff.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/read_resume_data.hpp" +#include "libtorrent/request_blocks.hpp" +#include "libtorrent/resolve_links.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/session_handle.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/session_types.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/sha1.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/sha256.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/socks5_stream.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/ssl.hpp" +#include "libtorrent/ssl_stream.hpp" +#include "libtorrent/stack_allocator.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/tailqueue.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/torrent_flags.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/udp_tracker_connection.hpp" +#include "libtorrent/union_endpoint.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/upnp.hpp" +#include "libtorrent/utf8.hpp" +#include "libtorrent/vector_utils.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/web_connection_base.hpp" +#include "libtorrent/web_peer_connection.hpp" +#include "libtorrent/write_resume_data.hpp" +#include "libtorrent/xml_parse.hpp" diff --git a/include/libtorrent/link.hpp b/include/libtorrent/link.hpp new file mode 100644 index 0000000..14d8dce --- /dev/null +++ b/include/libtorrent/link.hpp @@ -0,0 +1,83 @@ +/* + +Copyright (c) 2010, 2013-2019, Arvid Norberg +Copyright (c) 2017, 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_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/lsd.hpp b/include/libtorrent/lsd.hpp new file mode 100644 index 0000000..353b368 --- /dev/null +++ b/include/libtorrent/lsd.hpp @@ -0,0 +1,95 @@ +/* + +Copyright (c) 2007, 2009, 2012, 2014-2015, 2017, 2019-2020, Arvid Norberg +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_LSD_HPP +#define TORRENT_LSD_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/aux_/lsd.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent { + +struct lsd : std::enable_shared_from_this +{ + lsd(io_context& ios, aux::lsd_callback& cb + , address listen_address, address 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& info_hash + , 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..62a0a29 --- /dev/null +++ b/include/libtorrent/magnet_uri.hpp @@ -0,0 +1,88 @@ +/* + +Copyright (c) 2007-2009, 2012-2013, 2016-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_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; + struct 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 + , 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/mmap_disk_io.hpp b/include/libtorrent/mmap_disk_io.hpp new file mode 100644 index 0000000..b58ba53 --- /dev/null +++ b/include/libtorrent/mmap_disk_io.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2007-2018, Steven Siloti +Copyright (c) 2007, 2013-2016, 2019-2020, Arvid Norberg +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_DISK_IO_THREAD +#define TORRENT_DISK_IO_THREAD + +#include "libtorrent/config.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/io_context.hpp" + +namespace libtorrent { + +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + + struct counters; + struct settings_interface; + + // constructs a memory mapped file disk I/O object. + TORRENT_EXPORT std::unique_ptr mmap_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); + +#endif // HAVE_MMAP || HAVE_MAP_VIEW_OF_FILE + +} + +#endif // TORRENT_DISK_IO_THREAD diff --git a/include/libtorrent/mmap_storage.hpp b/include/libtorrent/mmap_storage.hpp new file mode 100644 index 0000000..1bfb330 --- /dev/null +++ b/include/libtorrent/mmap_storage.hpp @@ -0,0 +1,227 @@ +/* + +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2003, 2009, 2011, 2013-2020, Arvid Norberg +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2018-2019, 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_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/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" +#include "libtorrent/aux_/open_mode.hpp" // for aux::open_mode_t +#include "libtorrent/disk_interface.hpp" // for disk_job_flags_t + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + +namespace aux { + struct session_settings; + struct file_view_pool; + struct file_view; +} + + struct TORRENT_EXTRA_EXPORT mmap_storage + : std::enable_shared_from_this + , aux::disk_job_fence + { + // constructs the mmap_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_view_pool`` is the cache of file mappings that the storage will use. + // All files it opens will ask the file_view_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. + mmap_storage(storage_params const& params, aux::file_view_pool&); + + // hidden + ~mmap_storage(); + mmap_storage(mmap_storage const&) = delete; + mmap_storage& operator=(mmap_storage const&) = delete; + + void abort_jobs(); + + bool has_any_file(storage_error&); + void set_file_priority(settings_interface const& + , aux::vector& prio + , storage_error&); + void rename_file(file_index_t index, std::string const& new_filename + , storage_error&); + void release_files(storage_error&); + void delete_files(remove_flags_t options, storage_error&); + status_t initialize(settings_interface const&, storage_error&); + std::pair move_storage(std::string save_path + , move_flags_t, storage_error&); + bool verify_resume_data(add_torrent_params const& rd + , aux::vector const& links + , storage_error&); + bool tick(); + + int readv(settings_interface const&, span bufs + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags + , storage_error&); + int writev(settings_interface const&, span bufs + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags + , storage_error&); + int hashv(settings_interface const&, hasher& ph, std::ptrdiff_t len + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags, storage_error&); + int hashv2(settings_interface const&, hasher256& ph, std::ptrdiff_t len + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags, storage_error&); + + // 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 : m_files; } + file_storage const& orig_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; } + + storage_index_t storage_index() const { return m_storage_index; } + void set_storage_index(storage_index_t st) { m_storage_index = st; } + + 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 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}; + + 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 + boost::optional open_file(settings_interface const&, file_index_t + , aux::open_mode_t, storage_error&) const; + boost::optional open_file_impl(settings_interface const& + , file_index_t, aux::open_mode_t, storage_error&) 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 + aux::file_view_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; + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + // Windows has a race condition when unmapping a view while a new + // view or mapping object is being created in a different thread. + // The race can cause a page of written data to be zeroed out before + // it is written out to disk. To avoid the race these calls must be + // serialized on a per-file basis. See github issue #3842 for details. + + // This array stores a mutex for each file in the storage object + // It must be aquired before calling CreateFileMapping or UnmapViewOfFile + mutable std::shared_ptr m_file_open_unmap_lock; +#endif + + bool m_allocate_files; + }; + +} + +#endif // TORRENT_STORAGE_HPP_INCLUDED diff --git a/include/libtorrent/natpmp.hpp b/include/libtorrent/natpmp.hpp new file mode 100644 index 0000000..cb0a523 --- /dev/null +++ b/include/libtorrent/natpmp.hpp @@ -0,0 +1,219 @@ +/* + +Copyright (c) 2007-2010, 2015-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2018, 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_NATPMP_HPP +#define TORRENT_NATPMP_HPP + +#include "libtorrent/io_context.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 +#include "libtorrent/aux_/listen_socket_handle.hpp" + +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); +} // namespace errors + + TORRENT_EXPORT boost::system::error_category& pcp_category(); +} // namespace libtorrent + +namespace boost { +namespace system { + template<> struct is_error_code_enum + { static const bool value = true; }; +} +} + +namespace libtorrent { + +struct TORRENT_EXTRA_EXPORT natpmp final + : std::enable_shared_from_this + , single_threaded +{ + natpmp(io_context& ios, aux::portmap_callback& cb, aux::listen_socket_handle ls); + + 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}; + + io_context& m_ioc; + + aux::listen_socket_handle m_listen_handle; + + bool m_disabled = false; + + bool m_abort = false; +}; + +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/netlink.hpp b/include/libtorrent/netlink.hpp new file mode 100644 index 0000000..0f15360 --- /dev/null +++ b/include/libtorrent/netlink.hpp @@ -0,0 +1,203 @@ +/* + +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, 2019, Arvid Norberg +Copyright (c) 2018, Eugene Shalygin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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..f46f377 --- /dev/null +++ b/include/libtorrent/operations.hpp @@ -0,0 +1,265 @@ +/* + +Copyright (c) 2015, 2017-2020, Arvid Norberg +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_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 of network routes + enum_route, + + // moving read/write position in a file, operation_t::hostname_lookup + file_seek, + + // an async wait operation on a timer + timer, + + // call to mmap() (or windows counterpart) + file_mmap, + + // call to ftruncate() (or SetEndOfFile() on windows) + file_truncate, + }; + + // 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..bf85715 --- /dev/null +++ b/include/libtorrent/optional.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2017, 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_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/parse_url.hpp b/include/libtorrent/parse_url.hpp new file mode 100644 index 0000000..5123cdb --- /dev/null +++ b/include/libtorrent/parse_url.hpp @@ -0,0 +1,66 @@ +/* + +Copyright (c) 2008-2009, 2014, 2016-2017, 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_PARSE_URL_HPP_INCLUDED +#define TORRENT_PARSE_URL_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include +#include + +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_view.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); + + // returns true if the hostname contains any IDNA (internationalized domain + // name) labels. + TORRENT_EXTRA_EXPORT bool is_idna(string_view hostname); + + // the query string is the part of the URL immediately following "?", i.e. + // the query string arguments. This function returns true if any of the + // arguments are "info_hash", "port", "key", "event", "uploaded", + // "downloaded", "left" or "corrupt". + TORRENT_EXTRA_EXPORT bool has_tracker_query_string(string_view query_string); +} + +#endif diff --git a/include/libtorrent/part_file.hpp b/include/libtorrent/part_file.hpp new file mode 100644 index 0000000..adbd736 --- /dev/null +++ b/include/libtorrent/part_file.hpp @@ -0,0 +1,138 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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" +#include "libtorrent/hasher.hpp" +#include "libtorrent/aux_/open_mode.hpp" +#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t + +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 path, std::string 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); + int hashv(hasher& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec); + int hashv2(hasher256& ph, std::ptrdiff_t len, 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(aux::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; + } + + template + int do_hashv(Hasher& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec); + + 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..3c2280e --- /dev/null +++ b/include/libtorrent/pe_crypto.hpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2007, Un Shyam +Copyright (c) 2007-2009, 2011-2012, 2014-2019, Arvid Norberg +Copyright (c) 2016-2017, 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_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/aux_/receive_buffer.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/span.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(); + + // 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(aux::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 + , aux::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..9d3b0e3 --- /dev/null +++ b/include/libtorrent/peer.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2003-2004, 2006, 2012, 2014-2017, 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_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..8d6f0a9 --- /dev/null +++ b/include/libtorrent/peer_class.hpp @@ -0,0 +1,159 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016, 2020, 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_PEER_CLASS_HPP_INCLUDED +#define TORRENT_PEER_CLASS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/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 + aux::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..0e2646c --- /dev/null +++ b/include/libtorrent/peer_class_set.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2010, 2013-2015, 2019, Arvid Norberg +Copyright (c) 2016-2017, 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_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..bc6815b --- /dev/null +++ b/include/libtorrent/peer_class_type_filter.hpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2014, 2016-2017, 2019, Arvid Norberg +Copyright (c) 2017, 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_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..0a00af1 --- /dev/null +++ b/include/libtorrent/peer_connection.hpp @@ -0,0 +1,1255 @@ +/* + +Copyright (c) 2016, Falcosc +Copyright (c) 2003-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/peer_id.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/aux_/bandwidth_limit.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/chained_buffer.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/aux_/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_context.hpp" +#include "libtorrent/aux_/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 "libtorrent/aux_/socket_type.hpp" + +#include +#include +#include +#include +#include // for std::forward +#include // for make_tuple +#include +#include + +namespace libtorrent { + + struct torrent; + struct torrent_peer; + struct disk_interface; + +#ifndef TORRENT_DISABLE_EXTENSIONS + struct peer_plugin; +#endif + +namespace aux { + + struct session_interface; + + struct min_value_t {}; + static const min_value_t min_value{}; + + struct relative_time + { + relative_time() : m_time_diff(0) {} + explicit relative_time(min_value_t) : m_time_diff(std::numeric_limits::min()) {} + void set(time_point const reference, time_point const new_value) noexcept + { + m_time_diff = duration_cast(new_value - reference); + } + + time_point get(time_point reference) const noexcept + { + return reference + m_time_diff; + } + private: + milliseconds32 m_time_diff; + }; + + template + T clamp_assign(int const v) + { + auto const limit = std::numeric_limits::max(); + if (v < 0) return 0; + if (v > int(limit)) return limit; + return static_cast(v); + } +} + + 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_context* ios; + std::weak_ptr tor; + aux::socket_type 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; + + struct TORRENT_EXTRA_EXPORT peer_connection + : peer_connection_hot_members + , aux::bandwidth_socket + , peer_class_set + , disk_observer + , peer_connection_interface + , std::enable_shared_from_this + { + friend struct invariant_access; + friend struct torrent; + friend struct cork; + + // explicitly disallow assignment, to silence msvc warning + peer_connection& operator=(peer_connection const&) = delete; + + void on_exception(std::exception const& e); + void on_error(error_code const& ec); + + virtual connection_type type() const = 0; + + enum channels + { + upload_channel, + download_channel, + num_channels + }; + + explicit peer_connection(peer_connection_args& 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 int(m_prefer_contiguous_blocks); + } + + bool on_parole() const; + + picker_options_t picker_options() const; + + void prefer_contiguous_blocks(int const num) + { + m_prefer_contiguous_blocks = aux::clamp_assign(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); + bool share_mode() const { return m_share_mode; } +#endif + + void set_upload_only(bool); + bool upload_only() const { return m_upload_only || is_seed() || m_have_all; } + + 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; } + + // get the info hash associated with this peer + // this will be a sha1 hash or truncated sha256 hash depending + // on which protocol version this connection is using + sha1_hash associated_info_hash() const; + + 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); + + aux::socket_type const& get_socket() const { return m_socket; } + aux::socket_type& get_socket() { 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.get(m_connect); } + + // 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(); + +#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; + + 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.get(m_connect); } + + // 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(info_hash_t const& ih); + + bool validate_piece_request(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_context& get_context() { 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); + + void do_update_interest(); + void fill_send_buffer(); + void on_disk_read_complete(disk_buffer_holder buffer + , storage_error const& error, peer_request const&, time_point issue_time); + void on_disk_write_complete(storage_error const& error + , peer_request const&, std::shared_ptr); + void on_seed_mode_hashed(piece_index_t piece + , sha1_hash const& piece_hash, aux::vector const& block_hashes + , storage_error const& error); + + // this is for a future per-block request feature +#if 0 + void on_hash2_complete(storage_error const& error, peer_request const& r + , sha256_hash const& hash); +#endif + int request_timeout() const; + void check_graceful_pause(); + + int wanted_transfer(int channel); + int request_bandwidth(int channel, int bytes = 0); + + aux::socket_type 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: + aux::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. + std::uint16_t 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: + aux::chained_buffer m_send_buffer; + private: + + // the disk thread to use to issue disk jobs to + disk_interface& m_disk_thread; + + // io service + io_context& 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_context running as long as we + // have peer connections + executor_work_guard m_work; + + // the time when we last got a part of a + // piece packet from this peer + aux::relative_time m_last_piece; + + // the time we sent a request to + // this peer the last time + aux::relative_time m_last_request; + + // the time we received the last + // piece request from the peer + aux::relative_time m_last_incoming_request{aux::min_value}; + + // the time when we unchoked this peer + aux::relative_time m_last_unchoke; + + // if we're unchoked by this peer, this + // was the time + aux::relative_time m_last_unchoked; + + // the time we last choked this peer. min_time() in + // case we never unchoked it + aux::relative_time m_last_choke{aux::min_value}; + + // timeouts + aux::relative_time m_last_receive; + aux::relative_time m_last_sent; + + // the last time we filled our send buffer with payload + // this is used for timeouts + aux::relative_time m_last_sent_payload; + + // 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/ + aux::relative_time m_requested; + + // the time when this peer sent us a not_interested message + // the last time. + aux::relative_time m_became_uninterested; + + // the time when we sent a not_interested message to + // this peer the last time. + aux::relative_time m_became_uninteresting; + + // the time when async_connect was called + // or when the incoming connection was established + time_point m_connect = 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 + std::uint16_t 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. + std::uint16_t 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; + + // 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. + std::uint16_t 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 has told us explicitly that it is only + // uploading. A seed is *implicitly* upload only, so this is not + // necessarily true. + 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..f6805f5 --- /dev/null +++ b/include/libtorrent/peer_connection_handle.hpp @@ -0,0 +1,158 @@ +/* + +Copyright (c) 2015, 2017, Steven Siloti +Copyright (c) 2016-2019, Arvid Norberg +Copyright (c) 2016-2018, 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_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 { + +struct 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..489afb3 --- /dev/null +++ b/include/libtorrent/peer_connection_interface.hpp @@ -0,0 +1,84 @@ +/* + +Copyright (c) 2014-2019, Arvid Norberg +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_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..0f3a480 --- /dev/null +++ b/include/libtorrent/peer_id.hpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2003, 2009, 2013, 2016-2017, 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_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..98920f4 --- /dev/null +++ b/include/libtorrent/peer_info.hpp @@ -0,0 +1,470 @@ +/* + +Copyright (c) 2003-2011, 2013-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016, Alden Torres +Copyright (c) 2019, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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" +#include "libtorrent/aux_/deprecated.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; + + using connection_type_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 human readable 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. This is UTF-8 + // encoded. + 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 outgoing_connection = 5_bit; + + // deprecated synonym for outgoing_connection + 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 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; + +#if TORRENT_ABI_VERSION <= 2 + using connection_type_t = libtorrent::connection_type_t; +#endif + // Regular bittorrent connection + static constexpr connection_type_t standard_bittorrent = 0_bit; + + // HTTP connection using the `BEP 19`_ protocol + static constexpr connection_type_t web_seed = 1_bit; + + // HTTP connection using the `BEP 17`_ protocol + static constexpr connection_type_t http_seed = 2_bit; + + // the kind of connection this peer uses. See connection_type_t. + connection_type_t connection_type; + +#if TORRENT_ABI_VERSION == 1 + // an estimate of the rate this peer is downloading at, in + // bytes per second. + TORRENT_DEPRECATED 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 int 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 static constexpr bandwidth_state_flags_t bw_torrent = bw_limit; + TORRENT_DEPRECATED 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 int upload_limit; + TORRENT_DEPRECATED 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 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..9305126 --- /dev/null +++ b/include/libtorrent/peer_list.hpp @@ -0,0 +1,270 @@ +/* + +Copyright (c) 2003-2005, 2007-2009, 2011-2012, 2014-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2009, Daniel Wallin +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016-2017, Alden Torres +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_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/aux_/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_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, pex_flags_t, torrent_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); + + 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..a96970b --- /dev/null +++ b/include/libtorrent/peer_request.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2004, 2009, 2013-2014, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2004, 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_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 byte 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..db79c0e --- /dev/null +++ b/include/libtorrent/performance_counters.hpp @@ -0,0 +1,500 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2019, 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_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_EXPORT counters + { + // internal + 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, + + // bittorrent message counters + // 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_outgoing_hash_request, + num_outgoing_hashes, + num_outgoing_hash_reject, + + num_piece_passed, + num_piece_failed, + + num_have_pieces, + num_total_pieces_added, + + num_blocks_written, + num_blocks_read, + num_blocks_hashed, + 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 + + // internal + 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 this + // mode, blocks are allowed to be requested from more than one peer at + // at time. + num_peers_end_game, + request_latency, + + 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_file_priority, + num_fenced_load_torrent, + num_fenced_clear_piece, + num_fenced_tick_storage, + + 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 isn'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..6310f3b --- /dev/null +++ b/include/libtorrent/pex_flags.hpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2018, 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_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 = 0_bit; + + // the peer is a seed + constexpr pex_flags_t pex_seed = 1_bit; + + // the peer supports the uTP, transport protocol over UDP. + constexpr pex_flags_t pex_utp = 2_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 = 3_bit; + + // protocol v2 + // this is not a standard flag, it is only used internally + constexpr pex_flags_t pex_lt_v2 = 7_bit; +} + +#endif + diff --git a/include/libtorrent/piece_block.hpp b/include/libtorrent/piece_block.hpp new file mode 100644 index 0000000..91091c2 --- /dev/null +++ b/include/libtorrent/piece_block.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, 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_PIECE_BLOCK_HPP_INCLUDED +#define TORRENT_PIECE_BLOCK_HPP_INCLUDED + +#include "libtorrent/units.hpp" + +namespace libtorrent { + + struct TORRENT_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..16c6a53 --- /dev/null +++ b/include/libtorrent/piece_block_progress.hpp @@ -0,0 +1,61 @@ +/* + +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2005, 2014, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016-2017, 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_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..81252da --- /dev/null +++ b/include/libtorrent/piece_picker.hpp @@ -0,0 +1,906 @@ +/* + +Copyright (c) 2003-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2019, 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_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" +#include "libtorrent/index_range.hpp" + +namespace libtorrent { + + struct torrent; + struct 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 bytes, out of those pieces, that are pad + // files + int pad_bytes; + // true if the last piece is part of the set + bool last_piece; + }; + + struct 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; + + // 5_bit is available + + // 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) + , hashing(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 blocks_per_piece. + // The 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; + +#if 1 + // set to 1 if there is an outstanding hash request for this piece + std::uint16_t hashing:1; + + // this is for a future per-block request feature +#else + // available for future use + std::uint16_t unused:1; + + // number of outstanding hash jobs for this piece + std::uint16_t hashing:15; + + // available for future use + std::uint16_t unused2:1; +#endif + }; + + piece_picker(std::int64_t total_size, int piece_size); + + 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, torrent_peer const*); + void dec_refcount(piece_index_t, torrent_peer const*); + + // 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); + void we_dont_have(piece_index_t); + + // 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(std::int64_t total_size, int piece_size); + int num_pieces() const { return int(m_piece_map.size()); } + + bool have_piece(piece_index_t) const; + + bool is_downloading(piece_index_t const 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, download_priority_t); + + // returns the priority for the piece at 'index' + download_priority_t piece_priority(piece_index_t) const; + + // returns the current piece priorities for all pieces + void piece_priorities(std::vector&) const; + + // This function returns a list of all blocks in pieces that this client + // has and that are interesting to download, in the + // ``interesting_blocks`` out-parameter. The blocks are returned in + // priority order. + // + // If the caller of this function decides to download a block, 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! + // + // ``pieces`` should be the bitfield of all pieces, indicating which + // pieces the client has, that can be requested from it. + // + // 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 caller. 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& ignore + , 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 started_hash_job(piece_index_t piece); + void completed_hash_job(piece_index_t piece); + + void mark_as_canceled(piece_block block, torrent_peer* peer); + void mark_as_finished(piece_block block, torrent_peer* peer); + + void set_pad_bytes(piece_index_t p, int bytes); + + // 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); + + // returns information about the given piece + void piece_info(piece_index_t, 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) const; + + // if a piece had a hash-failure, it must be restored and + // made available for redownloading + void restore_piece(piece_index_t, span blocks = {}); + + // 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) const; + + // returns true if at least one block in this piece is being hashed + // only valid for v2 torrents + bool is_hashing(piece_index_t piece) const; + + // returns true if we have the piece or if the piece + // has passed the hash check + bool has_piece_passed(piece_index_t) const; + + // returns the number of blocks there is in the given piece + int blocks_in_piece(piece_index_t) const; + + // return the peer pointers to all peers that participated in + // this piece + std::vector get_downloaders(piece_index_t) 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_bytes_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(span 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 blocks_per_piece elements in it + span blocks_for_piece(downloading_piece const& dp) const; + + private: + + // 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; + + int block_size() const + { + TORRENT_ASSERT(m_piece_size > 0); + TORRENT_ASSERT(default_block_size > 0); + return (std::min)(m_piece_size, default_block_size); + } + int blocks_per_piece() const; + int piece_size(piece_index_t p) const; + + 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_bytes() const { return m_num_pad_bytes; } + + 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; + index_range + expand_piece(piece_index_t piece, int contiguous_blocks + , 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); + 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; + + // tracks the number of bytes in a specific piece that are part of a pad + // file. The padding is assumed to be at the end of the piece, and the + // blocks covered by the pad bytes are not picked by the piece picker + 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 bytes of pad file set in this piece picker + int m_num_pad_bytes = 0; + + // the number of pad blocks that we already have + int m_have_pad_bytes = 0; + + // the number of pad blocks part of filtered pieces we don't have + int m_filtered_pad_bytes = 0; + + // the number of pad blocks we have that are also filtered + int m_have_filtered_pad_bytes = 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 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_in_last_piece = 0; + int m_piece_size = 0; + std::int64_t m_total_size = 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..311008e --- /dev/null +++ b/include/libtorrent/platform_util.hpp @@ -0,0 +1,14 @@ +#ifndef TORRENT_PLATFORM_UTIL_HPP +#define TORRENT_PLATFORM_UTIL_HPP + +#include + +namespace libtorrent { + + int max_open_files(); + + void set_thread_name(char const* name); + +} + +#endif // TORRENT_PLATFORM_UTIL_HPP diff --git a/include/libtorrent/portmap.hpp b/include/libtorrent/portmap.hpp new file mode 100644 index 0000000..65a5c78 --- /dev/null +++ b/include/libtorrent/portmap.hpp @@ -0,0 +1,58 @@ +/* + +Copyright (c) 2017-2019, Arvid Norberg +Copyright (c) 2018, 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_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/posix_disk_io.hpp b/include/libtorrent/posix_disk_io.hpp new file mode 100644 index 0000000..487ac41 --- /dev/null +++ b/include/libtorrent/posix_disk_io.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2016-2017, 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_POSIX_DISK_IO +#define TORRENT_POSIX_DISK_IO + +#include "libtorrent/config.hpp" +#include "libtorrent/io_context.hpp" + +#include + +namespace libtorrent { + + struct counters; + struct disk_interface; + struct settings_interface; + + // this is a simple posix disk I/O back-end, used for systems that don't + // have a 64 bit virtual address space or don't support memory mapped files. + // It's implemented using portable C file functions and is single-threaded. + TORRENT_EXPORT std::unique_ptr posix_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); +} + +#endif + diff --git a/include/libtorrent/proxy_base.hpp b/include/libtorrent/proxy_base.hpp new file mode 100644 index 0000000..0c53300 --- /dev/null +++ b/include/libtorrent/proxy_base.hpp @@ -0,0 +1,344 @@ +/* + +Copyright (c) 2007-2011, 2013-2015, 2017-2020, Arvid Norberg +Copyright (c) 2016, 2019, Alden Torres +Copyright (c) 2017, Jan Berkel +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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_context.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" + +namespace libtorrent { + +struct proxy_base +{ + 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_context& io_context); + ~proxy_base(); + proxy_base(proxy_base&&) noexcept = default; + proxy_base& operator=(proxy_base&&) = default; + proxy_base(proxy_base const&) = delete; + proxy_base& operator=(proxy_base const&) = delete; + + void set_proxy(std::string hostname, int port) + { + m_hostname = std::move(hostname); + m_port = port; + } + + using executor_type = tcp::socket::executor_type; + executor_type get_executor() { return m_sock.get_executor(); } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler handler) + { + m_sock.async_read_some(buffers, std::move(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 handler) + { + m_sock.async_write_some(buffers, std::move(handler)); + } + +#if BOOST_VERSION >= 106600 && !defined TORRENT_BUILD_SIMULATOR + // Compatiblity with the async_wait method introduced in boost 1.66 + + static constexpr auto wait_read = tcp::socket::wait_read; + static constexpr auto wait_write = tcp::socket::wait_write; + static constexpr auto wait_error = tcp::socket::wait_error; + + template + void async_wait(tcp::socket::wait_type type, Handler handler) + { + m_sock.async_wait(type, std::move(handler)); + } +#endif + +#ifndef BOOST_NO_EXCEPTIONS + void non_blocking(bool b) + { + m_sock.non_blocking(b); + } +#endif + + void non_blocking(bool b, error_code& ec) + { + m_sock.non_blocking(b, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const& opt) + { + m_sock.set_option(opt); + } +#endif + + template + void set_option(SettableSocketOption const& opt, error_code& ec) + { + m_sock.set_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption& opt) + { + m_sock.get_option(opt); + } +#endif + + template + void get_option(GettableSocketOption& opt, error_code& ec) + { + m_sock.get_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void bind(endpoint_type const& /* endpoint */) + { +// m_sock.bind(endpoint); + } +#endif + + void cancel() + { + m_sock.cancel(); + } + + void cancel(error_code& ec) + { + 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); + } + + 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: + + // The handler must be taken as lvalue reference here since we may not call + // it. But if we do, we want the call operator to own the function object. + template + bool handle_error(error_code const& e, Handler&& h) + { + if (!e) return false; + std::forward(h)(e); + error_code ec; + close(ec); + return true; + } + + aux::noexcept_movable m_sock; + std::string m_hostname; // proxy host + int m_port; // proxy port + + aux::noexcept_movable m_remote_endpoint; + + // TODO: 2 use the resolver interface that has a built-in cache + aux::noexcept_move_only m_resolver; +}; + +template +struct wrap_allocator_t +{ + wrap_allocator_t(Handler h, UnderlyingHandler uh) + : m_handler(std::move(h)) + , m_underlying_handler(std::move(uh)) + {} + + wrap_allocator_t(wrap_allocator_t const&) = default; + wrap_allocator_t(wrap_allocator_t&&) = default; + + template + void operator()(A&&... a) + { + m_handler(std::forward(a)..., std::move(m_underlying_handler)); + } + + using allocator_type = typename boost::asio::associated_allocator::type; + using executor_type = typename boost::asio::associated_executor::type; + + allocator_type get_allocator() const noexcept + { return boost::asio::get_associated_allocator(m_underlying_handler); } + + executor_type get_executor() const noexcept + { + return boost::asio::get_associated_executor(m_underlying_handler); + } + +private: + Handler m_handler; + UnderlyingHandler m_underlying_handler; +}; + +template +wrap_allocator_t wrap_allocator(Handler h, UnderlyingHandler u) +{ + return wrap_allocator_t{std::move(h), std::move(u)}; +} + + +} + +#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..41a9dec --- /dev/null +++ b/include/libtorrent/random.hpp @@ -0,0 +1,85 @@ +/* + +Copyright (c) 2011-2013, 2016-2019, Arvid Norberg +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_RANDOM_HPP_INCLUDED +#define TORRENT_RANDOM_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" + +#include +#include +#include + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT std::uint32_t random(std::uint32_t m); + +namespace aux { + + TORRENT_EXTRA_EXPORT std::mt19937& random_engine(); + + template + void random_shuffle(Range& range) + { +#ifdef TORRENT_BUILD_SIMULATOR + // in simulations, we want all shuffles to be deterministic (as long as + // the random engine is deterministic + if (range.size() == 0) return; + for (auto i = range.size() - 1; i > 0; --i) { + auto const other = random(std::uint32_t(i)); + if (i == other) continue; + using std::swap; + swap(range.data()[i], range.data()[other]); + } +#else + std::shuffle(range.data(), range.data() + range.size(), random_engine()); +#endif + } + + // Fills the buffer with pseudo 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); + + // Fills the buffer with random bytes from a strong entropy source. This can + // be used to generate secrets. + TORRENT_EXTRA_EXPORT void crypto_random_bytes(span buffer); +} +} + +#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..db57f1f --- /dev/null +++ b/include/libtorrent/read_resume_data.hpp @@ -0,0 +1,71 @@ +/* + +Copyright (c) 2015-2018, 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_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" +#include "libtorrent/torrent_info.hpp" // for load_torrent_limits + +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. + // + // The ``piece_limit`` parameter determines the largest number of pieces + // allowed in the torrent that may be loaded as part of the resume data, if + // it contains an ``info`` field. The overloads that take a flat buffer are + // instead configured with limits on torrent sizes via load_torrent limits. + // + // In order to support large torrents, it may also be necessary to raise the + // settings_pack::max_piece_count setting and pass a higher limit to calls + // to torrent_info::parse_info_section(). + TORRENT_EXPORT add_torrent_params read_resume_data(bdecode_node const& rd + , error_code& ec, int piece_limit = 0x200000); + TORRENT_EXPORT add_torrent_params read_resume_data(span buffer + , error_code& ec, load_torrent_limits const& cfg = {}); + TORRENT_EXPORT add_torrent_params read_resume_data(bdecode_node const& rd + , int piece_limit = 0x200000); + TORRENT_EXPORT add_torrent_params read_resume_data(span buffer + , load_torrent_limits const& cfg = {}); +} + +#endif diff --git a/include/libtorrent/request_blocks.hpp b/include/libtorrent/request_blocks.hpp new file mode 100644 index 0000000..fb01a8e --- /dev/null +++ b/include/libtorrent/request_blocks.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2010, 2014-2015, 2017, 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_REQUEST_BLOCKS_HPP_INCLUDED +#define TORRENT_REQUEST_BLOCKS_HPP_INCLUDED + +#include "libtorrent/peer_info.hpp" + +namespace libtorrent { + + struct torrent; + struct 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..cc4439e --- /dev/null +++ b/include/libtorrent/resolve_links.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +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_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" +#include "libtorrent/fwd.hpp" +#include "libtorrent/sha1_hash.hpp" // for sha256_hash + +namespace libtorrent { + +#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: + + void match_v1(std::shared_ptr const& ti + , std::string const& save_path); + void match_v2(std::shared_ptr const& ti + , std::string const& save_path); + + // 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; + + // maps file root hash to file index, in m_torrent_file + std::unordered_multimap m_file_roots; + }; +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + +} + +#endif diff --git a/include/libtorrent/session.hpp b/include/libtorrent/session.hpp new file mode 100644 index 0000000..38ca9a6 --- /dev/null +++ b/include/libtorrent/session.hpp @@ -0,0 +1,299 @@ +/* + +Copyright (c) 2003-2004, 2006-2007, 2009-2010, 2013-2020, Arvid Norberg +Copyright (c) 2015, Steven Siloti +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_SESSION_HPP_INCLUDED +#define TORRENT_SESSION_HPP_INCLUDED + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/session_handle.hpp" +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/session_types.hpp" // for session_flags_t + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/fingerprint.hpp" +#include // for snprintf +#endif + +namespace libtorrent { + +TORRENT_VERSION_NAMESPACE_3 + struct plugin; + struct session_params; +TORRENT_VERSION_NAMESPACE_3_END + + // 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; + struct settings_interface; + + // the constructor function for the default storage. On systems that support + // memory mapped files (and a 64 bit address space) the memory mapped storage + // will be constructed, otherwise the portable posix storage. + TORRENT_EXPORT std::unique_ptr default_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); + + // 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. + struct TORRENT_EXPORT session_proxy + { + friend struct session; + // 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 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(). + struct TORRENT_EXPORT session : session_handle + { + // 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. + // + // The overloads taking ``flags`` can be used to start a session in + // paused mode (by passing in ``session::paused``). Note that + // ``add_default_plugins`` do not have an affect on constructors that + // take a session_params object. It already contains the plugins to use. + explicit session(session_params const& params); + explicit session(session_params&& params); + session(session_params const& params, session_flags_t flags); + session(session_params&& params, session_flags_t flags); + session(); + + // Overload of the constructor that takes an external io_context to run + // the session object on. This is primarily useful for tests that may want + // to run multiple sessions on a single io_context, or low resource + // systems where additional threads are expensive and sharing an + // io_context with other events is fine. + // + // .. warning:: + // The session object does not cleanly terminate with an external + // ``io_context``. The ``io_context::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_context, then + // destruct the session_proxy object. + session(session_params&& params, io_context& ios); + session(session_params const& params, io_context& ios); + session(session_params&& params, io_context& ios, session_flags_t); + session(session_params const& params, io_context& ios, session_flags_t); + + // hidden + session(session&&); + session& operator=(session&&) &; + + // hidden + session(session const&) = delete; + session& operator=(session const&) = delete; + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // 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. + TORRENT_DEPRECATED + session(settings_pack&& pack, session_flags_t const flags); + TORRENT_DEPRECATED + session(settings_pack const& pack, session_flags_t const flags); + explicit session(settings_pack&& pack) : session(std::move(pack), add_default_plugins) {} + explicit session(settings_pack const& pack) : session(pack, add_default_plugins) {} + + // overload of the constructor that takes an external io_context to run + // the session object on. This is primarily useful for tests that may want + // to run multiple sessions on a single io_context, or low resource + // systems where additional threads are expensive and sharing an + // io_context with other events is fine. + // + // .. warning:: + // The session object does not cleanly terminate with an external + // ``io_context``. The ``io_context::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_context, then + // destruct the session_proxy object. + TORRENT_DEPRECATED + session(settings_pack&&, io_context&, session_flags_t); + TORRENT_DEPRECATED + session(settings_pack const&, io_context&, session_flags_t); + session(settings_pack&& pack, io_context& ios) : session(std::move(pack), ios, add_default_plugins) {} + session(settings_pack const& pack, io_context& ios) : session(pack, ios, add_default_plugins) {} + +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // TORRENT_ABI_VERSION + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + 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); + + 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); + +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#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:: + // + // struct session_proxy {}; + session_proxy abort(); + + private: + + void start(session_flags_t, session_params&& params, io_context* ios); + +#if TORRENT_ABI_VERSION <= 2 + void start(session_flags_t flags, settings_pack&& sp, io_context* ios); +#endif + + void start(session_params const& params, io_context* ios) = delete; +#if TORRENT_ABI_VERSION <= 2 + void start(session_flags_t flags, settings_pack const& sp, io_context* ios) = delete; +#endif + + // 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..ad40a4b --- /dev/null +++ b/include/libtorrent/session_handle.hpp @@ -0,0 +1,1126 @@ +/* + +Copyright (c) 2014-2018, Steven Siloti +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2015-2018, Alden Torres +Copyright (c) 2020, AllSeeingEyeTolledEweSew +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 // for shared_ptr + +#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/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_context.hpp" +#include "libtorrent/session_types.hpp" +#include "libtorrent/portmap.hpp" // for portmap_protocol + +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/kademlia/announce_flags.hpp" + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/kademlia/dht_settings.hpp" +#endif + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/session_settings.hpp" +#include +#endif + +#include "libtorrent/extensions.hpp" +#include "libtorrent/session_types.hpp" // for session_flags_t + +namespace libtorrent { + + struct 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 struct 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; + +#if TORRENT_ABI_VERSION <= 2 + // saves dht_settings. All DHT settings are now part of the main + // settings_pack, and saved by setting the save_settings flag + TORRENT_DEPRECATED static constexpr save_state_flags_t save_dht_settings = 1_bit; +#endif + + // 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 + TORRENT_DEPRECATED static constexpr save_state_flags_t save_encryption_settings = 3_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_as_map = 4_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_proxy = 5_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_i2p_proxy = 6_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_dht_proxy = 7_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_peer_proxy = 8_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_web_proxy = 9_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_tracker_proxy = 10_bit; +#endif + + // load or save state from plugins + static constexpr save_state_flags_t save_extension_state = 11_bit; + + // load or save the IP filter set on the session + static constexpr save_state_flags_t save_ip_filter = 12_bit; + +#if TORRENT_ABI_VERSION <= 2 + // deprecated in 2.0 + // instead of these functions, use session_state() below, and restore + // state using the session_params on session construction. + + // 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(). + TORRENT_DEPRECATED + void save_state(entry& e, save_state_flags_t flags = save_state_flags_t::all()) const; + TORRENT_DEPRECATED + void load_state(bdecode_node const& e, save_state_flags_t flags = save_state_flags_t::all()); +#endif + + // returns the current session state. This can be passed to + // write_session_params() to save the state to disk and restored using + // read_session_params() when constructing a new session. The kind of + // state that's included is all settings, the DHT routing table, possibly + // plugin-specific state. + // the flags parameter can be used to only save certain parts of the + // session state + session_params session_state(save_state_flags_t flags = save_state_flags_t::all()) 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. + 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_context& get_context(); + + // set the DHT state for the session. This will be taken into account the + // next time the DHT is started, as if it had been passed in via the + // session_params on startup. + void set_dht_state(dht::dht_state const& st); + void set_dht_state(dht::dht_state&& st); + + // ``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); + + // 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 + , client_data_t userdata = {}); +#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 1.2 + TORRENT_DEPRECATED + void get_torrent_status(std::vector* ret + , std::function const& pred + , status_flags_t flags = {}) const; + + // ``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 + +#if TORRENT_ABI_VERSION <= 2 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + // ``set_dht_settings`` sets some parameters available to the dht node. + // See dht_settings for more information. + // + // ``get_dht_settings()`` returns the current settings + void set_dht_settings(dht::dht_settings const& settings); + dht::dht_settings get_dht_settings() const; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + + // ``is_dht_running()`` returns true if the DHT support has been started + // and false otherwise. + bool is_dht_running() 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, client_data_t userdata = {}); + +#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`: reference-Plugins.html + void add_extension(std::function( + torrent_handle const&, client_data_t)> ext); + void add_extension(std::shared_ptr ext); + +#if TORRENT_ABI_VERSION == 1 + // 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; +#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 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; + +#if TORRENT_ABI_VERSION <= 2 + // this will add common extensions like ut_pex, ut_metadata, lt_tex + // smart_ban and possibly others. + TORRENT_DEPRECATED static constexpr session_flags_t add_default_plugins = 0_bit; +#endif + +#if TORRENT_ABI_VERSION == 1 + // this will start features like DHT, local service discovery, UPnP + // and NAT-PMP. + TORRENT_DEPRECATED static constexpr session_flags_t start_default_features = 1_bit; +#endif + + // when set, the session will start paused. Call + // session_handle::resume() to start + static constexpr session_flags_t paused = 2_bit; + + // ``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. + // + // remove_torrent() is non-blocking, but will remove the torrent from the + // session synchronously. Calling session_handle::add_torrent() immediately + // afterward with the same torrent will succeed. Note that this creates a + // new handle which is not equal to the removed one. + // + // 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``. Once the torrent is deleted, a + // torrent_deleted_alert is posted. + // + // The torrent_handle remains valid for some time after remove_torrent() is + // called. It will become invalid only after all libtorrent tasks (such as + // I/O tasks) release their references to the torrent. Until this happens, + // torrent_handle::is_valid() will return true, and other calls such + // as torrent_handle::status() will succeed. Because of this, and because + // remove_torrent() is non-blocking, the following sequence usually + // succeeds (does not throw system_error): + // .. code:: c++ + // + // session.remove_handle(handle); + // handle.save_resume_data(); + // + // 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&, remove_flags_t = {}); + + // 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&); + void apply_settings(settings_pack&&); + settings_pack get_settings() const; + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // deprecated in libtorrent 1.1. use settings_pack instead + TORRENT_DEPRECATED + void set_pe_settings(pe_settings const&); + TORRENT_DEPRECATED + pe_settings get_pe_settings() const; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // ``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&); + 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 + // 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_params.hpp b/include/libtorrent/session_params.hpp new file mode 100644 index 0000000..49b0eb2 --- /dev/null +++ b/include/libtorrent/session_params.hpp @@ -0,0 +1,156 @@ +/* + +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2019-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_SESSION_PARAMS_HPP_INCLUDED +#define TORRENT_SESSION_PARAMS_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/kademlia/dht_state.hpp" +#include "libtorrent/session_types.hpp" +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/ip_filter.hpp" + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/kademlia/dht_settings.hpp" +#endif + +namespace libtorrent { + +TORRENT_VERSION_NAMESPACE_3 +struct plugin; +TORRENT_VERSION_NAMESPACE_3_END + +struct disk_interface; +struct counters; + +using disk_io_constructor_type = std::function( + io_context&, settings_interface const&, counters&)>; + +TORRENT_VERSION_NAMESPACE_3 + +// 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). Pass a settings_pack to set the + // initial settings when the session starts. + session_params(settings_pack&& sp); // NOLINT + session_params(settings_pack const& sp); // NOLINT + session_params(); + + // hidden + ~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&); + session_params(session_params&&); + session_params& operator=(session_params const&) &; + session_params& operator=(session_params&&) &; + + // The settings to configure the session with + settings_pack settings; + + // the plugins to add to the session as it is constructed + std::vector> extensions; + +#if TORRENT_ABI_VERSION <= 2 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // this is deprecated. Use the dht_* settings instead. + dht::dht_settings dht_settings; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + + // DHT node ID and node addresses to bootstrap the DHT with. + dht::dht_state dht_state; + + // function object to construct the storage object for DHT items. + dht::dht_storage_constructor_type dht_storage_constructor; + + // function object to create the disk I/O subsystem. Defaults to + // default_disk_io_constructor. + disk_io_constructor_type disk_io_constructor; + + // this container can be used by extensions/plugins to store settings. It's + // primarily here to make it convenient to save and restore state across + // sessions, using read_session_params() and write_session_params(). + std::map ext_state; + + // the IP filter to use for the session. This restricts which peers are allowed + // to connect. As if passed to set_ip_filter(). + libtorrent::ip_filter ip_filter; +}; + +TORRENT_VERSION_NAMESPACE_3_END + +// These functions serialize and de-serialize a ``session_params`` object to and +// from bencoded form. The session_params object is used to initialize a new +// session using the state from a previous one (or by programmatically configure +// the session up-front). +// The flags parameter can be used to only save and load certain aspects of the +// session's state. +// The ``_buf`` suffix indicates the function operates on buffer rather than the +// bencoded structure. +// The torrents in a session are not part of the session_params state, they have +// to be restored separately. +TORRENT_EXPORT session_params read_session_params(bdecode_node const& e + , save_state_flags_t flags = save_state_flags_t::all()); +TORRENT_EXPORT session_params read_session_params(span buf + , save_state_flags_t flags = save_state_flags_t::all()); +TORRENT_EXPORT entry write_session_params(session_params const& sp + , save_state_flags_t flags = save_state_flags_t::all()); +TORRENT_EXPORT std::vector write_session_params_buf(session_params const& sp + , save_state_flags_t flags = save_state_flags_t::all()); + +} + +#endif diff --git a/include/libtorrent/session_settings.hpp b/include/libtorrent/session_settings.hpp new file mode 100644 index 0000000..a4ae9d6 --- /dev/null +++ b/include/libtorrent/session_settings.hpp @@ -0,0 +1,118 @@ +/* + +Copyright (c) 2006-2007, 2010, 2013-2020, Arvid Norberg +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_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 { + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + using dht_settings = dht::dht_settings; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + 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..d36fb86 --- /dev/null +++ b/include/libtorrent/session_stats.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2015, 2017-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_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 + TORRENT_DEPRECATED static constexpr metric_type_t type_counter = metric_type_t::counter; + TORRENT_DEPRECATED static constexpr metric_type_t 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..c7f7436 --- /dev/null +++ b/include/libtorrent/session_status.hpp @@ -0,0 +1,238 @@ +/* + +Copyright (c) 2006, 2008-2011, 2013-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Falcosc +Copyright (c) 2017, Falco +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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..f794cdc --- /dev/null +++ b/include/libtorrent/session_types.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2017, 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_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; + + // The flags type used to specify options to removing files of torrents + 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..d85faff --- /dev/null +++ b/include/libtorrent/settings_pack.hpp @@ -0,0 +1,2176 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2018, TheOriginalWinCat +Copyright (c) 2019, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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(settings_pack const& sett, entry::dictionary_type& out); + TORRENT_EXTRA_EXPORT settings_pack non_default_settings(aux::session_settings const& 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); + + // 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 common interface to settings_pack and the internal representation of + // settings. + struct TORRENT_EXPORT settings_interface + { + virtual void set_str(int name, std::string val) = 0; + virtual void set_int(int name, int val) = 0; + virtual void set_bool(int name, bool val) = 0; + virtual bool has_val(int name) const = 0; + + virtual std::string const& get_str(int name) const = 0; + virtual int get_int(int name) const = 0; + virtual bool get_bool(int name) const = 0; + + template + // hidden + void set_int(int name, flags::bitfield_flag const val) + { set_int(name, static_cast(static_cast(val))); } + + // hidden + // these are here just to suppress the warning about virtual destructors + // internal + settings_interface() = default; + settings_interface(settings_interface const&) = default; + settings_interface(settings_interface&&) = default; + settings_interface& operator=(settings_interface const&) = default; + settings_interface& operator=(settings_interface&&) = default; + protected: + ~settings_interface() = default; + }; + + // 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 final : settings_interface + { + 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) override; + void set_int(int name, int val) override; + void set_bool(int name, bool val) override; + 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 override; + + // 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 override; + int get_int(int name) const override; + bool get_bool(int name) const override; + + // 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 + }; + + // internal + template + void for_each(Fun&& f) const + { + for (auto const& s : m_strings) f(s.first, s.second); + for (auto const& i : m_ints) f(i.first, i.second); + for (auto const& b : m_bools) f(b.first, b.second); + } + + // 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. + // + // When outgoing interfaces are specified, incoming connections or + // packets sent to a local interface or IP that's *not* in this list + // will be rejected with a peer_blocked_alert with + // ``invalid_local_interface`` as the reason. + // + // Note that these are just interface/adapter names or IP addresses. + // There are no ports specified in this list. IPv6 addresses without + // port should be specified without enclosing ``[``, ``]``. + 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, + +#if TORRENT_ABI_VERSION == 1 + // 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 TORRENT_DEPRECATED_ENUM, + 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, + + // 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 TORRENT_DEPRECATED_ENUM, + coalesce_writes TORRENT_DEPRECATED_ENUM, +#else + deprecated_use_read_cache, + deprecated_use_write_cache, + deprecated_flush_write_cache, + deprecated_coalesce_reads, + deprecated_coalesce_writes, +#endif + + // 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 + +#if TORRENT_ABI_VERSION <= 2 + // ``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 TORRENT_DEPRECATED_ENUM, +#else + deprecated_volatile_read_cache, +#endif + +#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 + + // Enables incoming and outgoing, TCP and uTP peer connections. + // ``false`` is disabled and ``true`` is enabled. When outgoing + // connections are disabled, libtorrent will simply not make + // outgoing peer connections with the specific transport protocol. + // Disabled incoming peer connections will simply be rejected. + // These options only apply to peer connections, not tracker- or any + // other kinds of connections. + 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. + // + // * A generic user-agent will be + // used for trackers (except for private torrents). + // * Your local IPv4 and IPv6 address won't be sent as query string + // parameters to private trackers. + // * If announce_ip is configured, it will not be sent to trackers + // * The client version will not be sent to peers in the extension + // handshake. + 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, + +#if TORRENT_ABI_VERSION <= 2 + // 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 TORRENT_DEPRECATED_ENUM, +#else + deprecated_allow_partial_disk_writes, +#endif + +#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 TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // if this is false, don't advertise support for the Tribler merkle + // tree piece message + support_merkle_torrents TORRENT_DEPRECATED_ENUM, +#else + deprecated_support_merkle_torrents, +#endif + + // 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, + + // 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 + dht_restrict_routing_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. + dht_restrict_search_ips, + + // 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. + dht_extended_routing_table, + + // 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. + dht_aggressive_lookups, + + // when set, perform lookups in a way that is slightly more expensive, + // but which minimizes the amount of information leaked about you. + dht_privacy_lookups, + + // 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". + dht_enforce_node_id, + + // ignore DHT messages from parts of the internet we wouldn't expect to + // see any traffic from + dht_ignore_dark_internet, + + // 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. + dht_read_only, + + // 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 and HTTPS web + // seeds will be validated against the system's certificate store + // (as defined by OpenSSL). If the system does not have a + // certificate store, this option may have to be disabled in order + // to get trackers and web seeds to work). + validate_https_trackers, + + // when enabled, tracker and web seed requests are subject to + // certain restrictions. + // + // An HTTP(s) tracker requests to localhost (loopback) + // must have the request path start with "/announce". This is the + // conventional bittorrent tracker request. Any other HTTP(S) + // tracker request to loopback will be rejected. This applies to + // trackers that redirect to loopback as well. + // + // Web seeds that end up on the client's local network (i.e. in a + // private IP address range) may not include query string arguments. + // This applies to web seeds redirecting to the local network as + // well. + // + // Web seeds on global IPs (i.e. not local network) may not redirect + // to a local network address + ssrf_mitigation, + + // when disabled, any tracker or web seed with an IDNA hostname + // (internationalized domain name) is ignored. This is a security + // precaution to avoid various unicode encoding attacks that might + // happen at the application level. + allow_idna, + + // when set to true, enables the attempt to use SetFileValidData() + // to pre-allocate disk space. This system call will only work when + // running with Administrator privileges on Windows, and so this + // setting is only relevant in that scenario. Using + // SetFileValidData() poses a security risk, as it may reveal + // previously deleted information from the disk. + enable_set_file_valid_data, + + // When using a SOCKS5 proxy, UDP traffic is routed through the + // proxy by sending a UDP ASSOCIATE command. If this option is true, + // the UDP ASSOCIATE command will include the IP address and + // listen port to the local UDP socket. This indicates to the proxy + // which source endpoint to expect our packets from. The benefit is + // that incoming packets can be forwarded correctly, before any + // outgoing packets are sent. The risk is that if there's a NAT + // between the client and the proxy, the IP address specified in the + // protocol may not be valid from the proxy's point of view. + socks5_udp_send_local_ep, + + 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, + +#if TORRENT_ABI_VERSION == 1 + // ``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). + // + // 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 TORRENT_DEPRECATED_ENUM, + + // Disk buffers are allocated using a pool allocator, the number of + // blocks that are allocated at a time when the pool needs to grow can + // be specified in ``cache_buffer_chunk_size``. Lower numbers saves + // memory at the expense of more heap allocations. If it is set to 0, + // the effective chunk size is proportional to the total cache size, + // attempting to strike a good balance between performance and memory + // usage. It defaults to 0. + cache_buffer_chunk_size TORRENT_DEPRECATED_ENUM, + + // ``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. + cache_expiry TORRENT_DEPRECATED_ENUM, +#else + deprecated_cache_size, + deprecated_cache_buffer_chunk_size, + deprecated_cache_expiry, +#endif + + // 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. + // write_through + // flush pieces to disk as they complete validation. + // + // 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, + // ``0x04`` represents Lower Effort. For more details see `RFC 8622`_. + // + // .. _`RFC 8622`: http://www.faqs.org/rfcs/rfc8622.html + 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 + +#if TORRENT_ABI_VERSION <= 2 + // ``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, +#else + deprecated_read_cache_line_size, + deprecated_write_cache_line_size, +#endif + + // ``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 + + // 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. + 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, + + // ``hashing_threads`` is the number of disk I/O threads to use for + // piece hash verification. These threads are *in addition* to the + // regular disk I/O threads specified by settings_pack::aio_threads. + // The hasher threads do not only compute hashes, but also perform + // the read from disk. On storage optimal for sequential access, + // such as hard drives, this setting should probably be set to 1. + hashing_threads, + + // 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, + +#if TORRENT_ABI_VERSION == 1 + // 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, +#else + deprecated_cache_size_volatile, +#endif + + // 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 240 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, + + // the maximum number of peers to send in a reply to ``get_peers`` + dht_max_peers_reply, + + // 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 + dht_search_branching, + + // 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. + dht_max_fail_count, + + // 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. + dht_max_torrents, + + // max number of items the DHT will store + dht_max_dht_items, + + // the max number of peers to store per torrent (for the DHT) + dht_max_peers, + + // the max number of torrents to return in a torrent search query to the + // DHT + dht_max_torrent_search_reply, + + // 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. + dht_block_timeout, + + // the max number of packets per second a DHT node is allowed to send + // without getting banned. + dht_block_ratelimit, + + // the number of seconds a immutable/mutable item will be expired. + // default is 0, means never expires. + dht_item_lifetime, + + // 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). + dht_sample_infohashes_interval, + + // 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 + dht_max_infohashes_sample_count, + + // ``max_piece_count`` is the maximum allowed number of pieces in + // metadata received via magnet links. Loading large torrents (with + // more pieces than the default limit) may also require passing in + // a higher limit to read_resume_data() and + // torrent_info::parse_info_section(), if those are used. + max_piece_count, + + // when receiving metadata (torrent file) from peers, this is the + // max number of bencoded tokens we're willing to parse. This limit + // is meant to prevent DoS attacks on peers. For very large + // torrents, this limit may have to be raised. + metadata_token_limit, + + max_int_setting_internal + }; + + // hidden + constexpr static int num_string_settings = int(max_string_setting_internal) - int(string_type_base); + constexpr static int num_bool_settings = int(max_bool_setting_internal) - int(bool_type_base); + constexpr static int 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, + + write_through = 3, + }; + + 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..185d0ee --- /dev/null +++ b/include/libtorrent/sha1.hpp @@ -0,0 +1,43 @@ +/* +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_CNG \ + && !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..41cfd74 --- /dev/null +++ b/include/libtorrent/sha1_hash.hpp @@ -0,0 +1,315 @@ +/* + +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +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_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 { + + // 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 or right ``n`` bits. + digest32& operator<<=(int n) & noexcept; + digest32& operator>>=(int n) & noexcept; + + // 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()); + } + +#if TORRENT_USE_IOSTREAM + // print a sha1_hash object to an ostream as 40 hexadecimal digits + friend std::ostream& operator<<(std::ostream& os, digest32 const& val) + { val.stream_out(os); return os; } + + // read 40 hexadecimal digits from an istream into a sha1_hash + friend std::istream& operator>>(std::istream& is, digest32& val) + { val.stream_in(is); return is; } +#endif // TORRENT_USE_IOSTREAM + + private: + +#if TORRENT_USE_IOSTREAM + void stream_in(std::istream& is); + void stream_out(std::ostream& os) const; +#endif // TORRENT_USE_IOSTREAM + + 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>; + using sha256_hash = digest32<256>; + +#if TORRENT_USE_IOSTREAM + extern template void digest32<160>::stream_out(std::ostream&) const; + extern template void digest32<256>::stream_out(std::ostream&) const; + extern template void digest32<160>::stream_in(std::istream&); + extern template void digest32<256>::stream_in(std::istream&); +#endif // TORRENT_USE_IOSTREAM + + extern template digest32<160>& digest32<160>::operator<<=(int) & noexcept; + extern template digest32<256>& digest32<256>::operator<<=(int) & noexcept; + extern template digest32<160>& digest32<160>::operator>>=(int) & noexcept; + extern template digest32<256>& digest32<256>::operator>>=(int) & noexcept; +} + +namespace std { + template + struct hash> + { + std::size_t operator()(libtorrent::digest32 const& k) const + { + std::size_t ret; + static_assert(N >= sizeof(ret) * 8, "hash is not defined for small digests"); + // this is OK because digest32 is already a hash + std::memcpy(&ret, k.data(), sizeof(ret)); + return ret; + } + }; +} + +#endif // TORRENT_SHA1_HASH_HPP_INCLUDED diff --git a/include/libtorrent/sha256.hpp b/include/libtorrent/sha256.hpp new file mode 100644 index 0000000..df96d26 --- /dev/null +++ b/include/libtorrent/sha256.hpp @@ -0,0 +1,33 @@ +// SHA-256. Adapted from LibTomCrypt. This code is Public Domain + +#ifndef TORRENT_SHA256_HPP_INCLUDED +#define TORRENT_SHA256_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if !defined TORRENT_USE_LIBGCRYPT \ + && !TORRENT_USE_COMMONCRYPTO \ + && !TORRENT_USE_CNG \ + && !TORRENT_USE_CRYPTOAPI_SHA_512 \ + && !defined TORRENT_USE_LIBCRYPTO + +#include + +namespace libtorrent { + + struct sha256_ctx + { + std::uint64_t length; + std::uint32_t state[8]; + std::uint32_t curlen; + std::uint8_t buf[64]; + }; + + TORRENT_EXTRA_EXPORT void SHA256_init(sha256_ctx& md); + TORRENT_EXTRA_EXPORT void SHA256_update(sha256_ctx& md + , std::uint8_t const* in, size_t len); + TORRENT_EXTRA_EXPORT void SHA256_final(std::uint8_t* digest, sha256_ctx& md); +} + +#endif +#endif diff --git a/include/libtorrent/sliding_average.hpp b/include/libtorrent/sliding_average.hpp new file mode 100644 index 0000000..b2c813f --- /dev/null +++ b/include/libtorrent/sliding_average.hpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2010, 2014, 2016-2019, Arvid Norberg +Copyright (c) 2019, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 // for is_integral + +#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..32c8c9c --- /dev/null +++ b/include/libtorrent/socket.hpp @@ -0,0 +1,298 @@ +/* + +Copyright (c) 2003-2004, 2006-2010, 2012, 2014-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2018, Alexandre Janniaux +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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_/noexcept_movable.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 +struct tcp : sim::asio::ip::tcp { + tcp(sim::asio::ip::tcp const& p) : sim::asio::ip::tcp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; +struct udp : sim::asio::ip::udp { + udp(sim::asio::ip::udp const& p) : sim::asio::ip::udp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; + using sim::asio::async_write; + using sim::asio::async_read; + using true_tcp_socket = sim::asio::ip::tcp::socket; +#else +struct tcp : boost::asio::ip::tcp { + tcp(boost::asio::ip::tcp const& p) : boost::asio::ip::tcp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; +struct udp : boost::asio::ip::udp { + udp(boost::asio::ip::udp const& p) : boost::asio::ip::udp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; + using boost::asio::async_write; + using boost::asio::async_read; + using true_tcp_socket = boost::asio::ip::tcp::socket; +#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(int 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(tos_t const 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; + }; + +#ifdef IP_DSCP_TRAFFIC_TYPE + struct dscp_traffic_type + { + explicit dscp_traffic_type(DWORD val) : m_value(val) {} + template + int level(Protocol const&) const { return IP_DSCP_TRAFFIC_TYPE; } + template + int name(Protocol const&) const { return DSCP_TRAFFIC_TYPE; } + template + DWORD const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + DWORD m_value; + }; +#endif + +#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_PROBE : 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..b0b6451 --- /dev/null +++ b/include/libtorrent/socket_io.hpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2009, 2013-2019, Arvid Norberg +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016-2017, 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_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 aux { + + 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_uint(), out); + } + else if (a.is_v6()) + { + for (auto b : a.to_v6().to_bytes()) + write_uint8(b, out); + } + } + + template + address_v4 read_v4_address(InIt&& in) + { + std::uint32_t const ip = read_uint32(in); + return address_v4(ip); + } + + template + address_v6 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 aux + +} + +#endif diff --git a/include/libtorrent/socket_type.hpp b/include/libtorrent/socket_type.hpp new file mode 100644 index 0000000..ae15d31 --- /dev/null +++ b/include/libtorrent/socket_type.hpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2019-2020, Arvid Norberg +Copyright (c) 2020, 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_SOCKET_TYPE_HPP +#define TORRENT_SOCKET_TYPE_HPP + +#include "libtorrent/config.hpp" +#include + +namespace libtorrent { + +// A type describing kinds of sockets involved in various operations or events. +enum class socket_type_t : std::uint8_t { + tcp, + socks5, + http, + utp, + i2p, + tcp_ssl, + socks5_ssl, + http_ssl, + utp_ssl, + +#if TORRENT_ABI_VERSION <= 2 + udp TORRENT_DEPRECATED_ENUM = utp, +#endif +}; + +// return a short human readable name for types of socket +// TODO: move to aux +char const* socket_type_name(socket_type_t); + +} + +#endif diff --git a/include/libtorrent/socks5_stream.hpp b/include/libtorrent/socks5_stream.hpp new file mode 100644 index 0000000..5e8c11c --- /dev/null +++ b/include/libtorrent/socks5_stream.hpp @@ -0,0 +1,561 @@ +/* + +Copyright (c) 2007, 2009-2010, 2013-2020, Arvid Norberg +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_SOCKS5_STREAM_HPP_INCLUDED +#define TORRENT_SOCKS5_STREAM_HPP_INCLUDED + +#include + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" // for is_ip_address +#include "libtorrent/assert.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/string_util.hpp" // for to_string +#include "libtorrent/socket_io.hpp" + +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 +} + +namespace boost { +namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + +} +} + +namespace libtorrent { + +// 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 + }; + + socks5_stream(socks5_stream&&) = default; + explicit socks5_stream(io_context& io_context) + : proxy_base(io_context) + , 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(!aux::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 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 + + ADD_OUTSTANDING_ASYNC("socks5_stream::name_lookup"); + m_resolver.async_resolve(m_hostname, to_string(m_port).data(), wrap_allocator( + [this](error_code const& ec, tcp::resolver::results_type ips, Handler hn) { + name_lookup(ec, std::move(ips), std::move(hn)); + }, std::move(handler))); + } + +private: + + template + void name_lookup(error_code const& e, tcp::resolver::results_type ips + , Handler h) + { + COMPLETE_ASYNC("socks5_stream::name_lookup"); + if (handle_error(e, std::move(h))) return; + + auto i = ips.begin(); + if (!m_sock.is_open()) + { + error_code ec; + m_sock.open(i->endpoint().protocol(), ec); + if (handle_error(ec, std::move(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(), wrap_allocator( + [this](error_code const& ec, Handler hn) + { connected(ec, std::move(hn)); }, std::move(h))); + } + + template + void connected(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connected"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + 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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake1(ec, std::move(hn)); + }, std::move(h))); + } + else if (m_version == 4) + { + socks_connect(std::move(h)); + } + else + { + std::move(h)(error_code(socks_error::unsupported_version)); + } + } + + template + void handshake1(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake1"); + if (handle_error(e, std::move(h))) return; + + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake2"); + m_buffer.resize(2); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake2(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake2(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake2"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + + char* p = &m_buffer[0]; + int version = read_uint8(p); + int method = read_uint8(p); + + if (version < m_version) + { + std::move(h)(error_code(socks_error::unsupported_version)); + return; + } + + if (method == 0) + { + socks_connect(std::move(h)); + } + else if (method == 2) + { + if (m_user.empty()) + { + std::move(h)(error_code(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), wrap_allocator( + [this](error_code const& ec, std::size_t const, Handler hn) { + handshake3(ec, std::move(hn)); + }, std::move(h))); + } + else + { + std::move(h)(error_code(socks_error::unsupported_authentication_method)); + return; + } + } + + template + void handshake3(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake3"); + if (handle_error(e, std::move(h))) return; + + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake4"); + m_buffer.resize(2); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake4(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake4(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake4"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + + char* p = &m_buffer[0]; + int version = read_uint8(p); + int status = read_uint8(p); + + if (version != 1) + { + std::move(h)(error_code(socks_error::unsupported_authentication_version)); + return; + } + + if (status != 0) + { + std::move(h)(error_code(socks_error::authentication_error)); + return; + } + + std::vector().swap(m_buffer); + socks_connect(std::move(h)); + } + + template + void socks_connect(Handler h) + { + using namespace libtorrent::aux; + + if (m_version == 5) + { + // send SOCKS5 connect command + m_buffer.resize(6 + (!m_dst_name.empty() + ? m_dst_name.size() + 1 + :(aux::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(aux::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 (!aux::is_v4(m_remote_endpoint)) + { + std::move(h)(error_code(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_uint(), p); + std::copy(m_user.begin(), m_user.end(), p); + p += m_user.size(); + write_uint8(0, p); // 0-terminator + } + else + { + std::move(h)(error_code(socks_error::unsupported_version)); + return; + } + + ADD_OUTSTANDING_ASYNC("socks5_stream::connect1"); + async_write(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + connect1(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connect1(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connect1"); + if (handle_error(e, std::move(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), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + connect2(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connect2(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connect2"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + + 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) + { + std::move(h)(error_code(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; + } + std::move(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); + std::move(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 + { + std::move(h)(error_code(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) + , wrap_allocator([this](error_code const& ec, std::size_t, Handler hn) { + connect3(ec, std::move(hn)); + }, std::move(h))); + } + else if (m_version == 4) + { + if (version != 0) + { + std::move(h)(error_code(socks_error::general_failure)); + return; + } + + // access granted + if (response == 90) + { + std::vector().swap(m_buffer); + std::move(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; + } + std::move(h)(ec); + } + } + + template + void connect3(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connect3"); + using namespace libtorrent::aux; + + if (handle_error(e, std::move(h))) return; + + std::vector().swap(m_buffer); + std::move(h)(e); + } + + // 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; +}; + +} + +#endif diff --git a/include/libtorrent/span.hpp b/include/libtorrent/span.hpp new file mode 100644 index 0000000..6a9b963 --- /dev/null +++ b/include/libtorrent/span.hpp @@ -0,0 +1,194 @@ +/* + +Copyright (c) 2016-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2019, Michael Cronenworth +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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.hpp b/include/libtorrent/ssl.hpp new file mode 100644 index 0000000..e91deb3 --- /dev/null +++ b/include/libtorrent/ssl.hpp @@ -0,0 +1,186 @@ +/* + +Copyright (c) 2020, Paul-Louis Ageneau +Copyright (c) 2018, Alexandre Janniaux +Copyright (c) 2020, Steven Siloti +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_SSL_HPP_INCLUDED +#define TORRENT_SSL_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/export.hpp" + +#if TORRENT_USE_SSL + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#ifndef BOOST_NO_EXCEPTIONS +#include +#endif + +#ifdef TORRENT_USE_OPENSSL +#include // for OPENSSL_VERSION_NUMBER +#if OPENSSL_VERSION_NUMBER < 0x1000000fL +#error OpenSSL too old, use a recent version with SNI support +#endif +#ifdef TORRENT_WINDOWS +// because openssl includes winsock.h, we must include winsock2.h first +#include +#endif +#include +#include +#include +#include +#endif + +#ifdef TORRENT_USE_GNUTLS +#include +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#endif + +#include +#include +#include +#include + +namespace libtorrent { +namespace ssl { + +using error_code = boost::system::error_code; + +#if defined TORRENT_USE_OPENSSL +#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 +using boost::asio::ssl::verify_context; +#if BOOST_VERSION >= 107300 +using boost::asio::ssl::host_name_verification; +#else +using host_name_verification = boost::asio::ssl::rfc2818_verification; +#endif + +using native_context_type = SSL_CTX*; +using native_stream_type = SSL*; +using context_handle_type = native_context_type; +using stream_handle_type = native_stream_type; + +typedef int (*server_name_callback_type)(SSL* s, int*, void* arg); + +#elif defined TORRENT_USE_GNUTLS +using boost::asio::gnutls::context; +using boost::asio::gnutls::stream_base; +using boost::asio::gnutls::stream; +using boost::asio::gnutls::verify_context; +using boost::asio::gnutls::host_name_verification; + +using native_context_type = context::native_handle_type; +using native_stream_type = stream_base::native_handle_type; +using context_handle_type = context*; +using stream_handle_type = stream_base*; + +typedef bool (*server_name_callback_type)(stream_handle_type handle, std::string const& name, void* arg); + +#endif + +namespace error { + +#if defined TORRENT_USE_OPENSSL +using boost::asio::error::get_ssl_category; +using boost::asio::ssl::error::get_stream_category; +#elif defined TORRENT_USE_GNUTLS +using boost::asio::gnutls::error::get_ssl_category; +using boost::asio::gnutls::error::get_stream_category; +#endif + +} + +inline context_handle_type get_handle(context &c) +{ +#if defined TORRENT_USE_OPENSSL + return c.native_handle(); +#elif defined TORRENT_USE_GNUTLS + return &c; +#endif +} + +template +stream_handle_type get_handle(stream& s) +{ +#if defined TORRENT_USE_OPENSSL + return s.native_handle(); +#elif defined TORRENT_USE_GNUTLS + return &s; +#endif +} + +template +context_handle_type get_context_handle(stream& s) +{ +#if defined TORRENT_USE_OPENSSL + return SSL_get_SSL_CTX(s.native_handle()); +#elif defined TORRENT_USE_GNUTLS + return &s.get_context(); +#endif +} + +TORRENT_EXTRA_EXPORT void set_trust_certificate(native_context_type nc, string_view pem, error_code &ec); + +TORRENT_EXTRA_EXPORT void set_server_name_callback(context_handle_type c, server_name_callback_type cb, void* arg, error_code& ec); +TORRENT_EXTRA_EXPORT void set_host_name(stream_handle_type s, std::string const& name, error_code& ec); + +TORRENT_EXTRA_EXPORT void set_context(stream_handle_type s, context_handle_type c); +TORRENT_EXTRA_EXPORT bool has_context(stream_handle_type s, context_handle_type c); +TORRENT_EXTRA_EXPORT context_handle_type get_context(stream_handle_type s); + +} // ssl +} // libtorrent + +#endif // TORRENT_USE_SSL + +#endif diff --git a/include/libtorrent/ssl_stream.hpp b/include/libtorrent/ssl_stream.hpp new file mode 100644 index 0000000..7a18f8d --- /dev/null +++ b/include/libtorrent/ssl_stream.hpp @@ -0,0 +1,354 @@ +/* + +Copyright (c) 2008, 2010-2020, Arvid Norberg +Copyright (c) 2018, Alexandre Janniaux +Copyright (c) 2018, Alden Torres +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_SSL + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/ssl.hpp" + +#include + +#include + +namespace libtorrent { + +template +struct ssl_stream +{ + ssl_stream(io_context& io_context, ssl::context& ctx) + : m_sock(new ssl::stream(io_context, ctx)) + {} + + template + ssl_stream(S&& s, ssl::context& ctx) + : m_sock(new ssl::stream(std::forward(s), ctx)) + {} + + ssl_stream(ssl_stream&&) = default; + + using sock_type = typename 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 + + ssl::stream_handle_type handle() + { + return ssl::get_handle(*m_sock); + } + + ssl::context_handle_type context_handle() + { + return ssl::get_context_handle(*m_sock); + } + + void set_host_name(std::string const& name) + { + error_code ec; + set_host_name(name, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } + + void set_host_name(std::string const& name, error_code& ec) + { + ssl::set_host_name(handle(), name, ec); + } + + template + void set_verify_callback(T const& fun, error_code& ec) + { + m_sock->set_verify_callback(fun, ec); + } + + template + void async_connect(endpoint_type const& endpoint, Handler const& h) + { + // the connect is split up in the following steps: + // 1. connect to peer + // 2. perform SSL client handshake + + m_sock->next_layer().async_connect(endpoint, wrap_allocator( + [this](error_code const& ec, Handler hn) { + connected(ec, std::move(hn)); + }, std::move(h))); + } + + template + void async_accept_handshake(Handler const& h) + { + // this is used for accepting SSL connections + m_sock->async_handshake(ssl::stream_base::server, wrap_allocator( + [this](error_code const& ec, Handler hn) { + handshake(ec, std::move(hn)); + }, std::move(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 handler) + { + error_code ec; + m_sock->next_layer().cancel(ec); + m_sock->async_shutdown(std::move(handler)); + } + + void shutdown(error_code& ec) + { + m_sock->shutdown(ec); + } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler handler) + { + m_sock->async_read_some(buffers, std::move(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 + void set_option(SettableSocketOption const& opt, error_code& ec) + { + 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 + void get_option(GettableSocketOption& opt, error_code& ec) + { + 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 + + void non_blocking(bool b, error_code& ec) + { m_sock->next_layer().non_blocking(b, ec); } + + template + void async_write_some(Const_Buffers const& buffers, Handler handler) + { + m_sock->async_write_some(buffers, std::move(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 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 m_sock->next_layer().remote_endpoint(); + } +#endif + + endpoint_type remote_endpoint(error_code& ec) const + { + return m_sock->next_layer().remote_endpoint(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type local_endpoint() const + { + return m_sock->next_layer().local_endpoint(); + } +#endif + + endpoint_type local_endpoint(error_code& ec) const + { + return m_sock->next_layer().local_endpoint(ec); + } + + lowest_layer_type& lowest_layer() + { + return m_sock->lowest_layer(); + } + + lowest_layer_type const& lowest_layer() const + { + return m_sock->lowest_layer(); + } + + next_layer_type& next_layer() + { + return m_sock->next_layer(); + } + + next_layer_type const& next_layer() const + { + return m_sock->next_layer(); + } + +private: + + template + void connected(error_code const& e, Handler h) + { + if (e) + { + h(e); + return; + } + + m_sock->async_handshake(ssl::stream_base::client, wrap_allocator( + [this](error_code const& ec, Handler hn) { + handshake(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake(error_code const& e, Handler h) + { + h(e); + } + + // to make us movable + std::unique_ptr> m_sock; +}; + +} + +#endif // TORRENT_USE_SSL + +#endif diff --git a/include/libtorrent/stack_allocator.hpp b/include/libtorrent/stack_allocator.hpp new file mode 100644 index 0000000..ec94f46 --- /dev/null +++ b/include/libtorrent/stack_allocator.hpp @@ -0,0 +1,96 @@ +/* + +Copyright (c) 2012, 2015-2019, Arvid Norberg +Copyright (c) 2016-2017, 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_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..5bdd1f3 --- /dev/null +++ b/include/libtorrent/stat.hpp @@ -0,0 +1,287 @@ +/* + +Copyright (c) 2003-2005, 2007-2012, 2014-2017, 2019, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +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_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..c24e7ce --- /dev/null +++ b/include/libtorrent/stat_cache.hpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2010, 2013-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_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..fd593aa --- /dev/null +++ b/include/libtorrent/storage.hpp @@ -0,0 +1,7 @@ + +#ifndef TORRENT_STORAGE_HPP_INCLUDED +#define TORRENT_STORAGE_HPP_INCLUDED + +#error "the disk I/O subsystem has been overhauled in libtorrent 2.0. storage_interface is no longer a customization point, customize disk_interface instead" + +#endif diff --git a/include/libtorrent/storage_defs.hpp b/include/libtorrent/storage_defs.hpp new file mode 100644 index 0000000..0d4a9c0 --- /dev/null +++ b/include/libtorrent/storage_defs.hpp @@ -0,0 +1,150 @@ +/* + +Copyright (c) 2006-2007, 2009, 2013-2014, 2016-2020, Arvid Norberg +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_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 { + + 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 mode minimizes + // fragmentation but could be a costly operation. + 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, + + // hidden + mask = 0xf, + + // this is not an enum value, but a flag that can be set in the return + // from async_check_files, in case an existing file was found larger than + // specified in the torrent. i.e. it has garbage at the end + // the status_t field is used for this to preserve ABI. + oversized_file = 0x10, + }; + + // internal + inline status_t operator|(status_t lhs, status_t rhs) + { + return status_t(static_cast(lhs) | static_cast(rhs)); + } + inline status_t operator&(status_t lhs, status_t rhs) + { + return status_t(static_cast(lhs) & static_cast(rhs)); + } + inline status_t operator~(status_t lhs) + { + return status_t(~static_cast(lhs)); + } + + // 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 + + // a parameter pack used to construct the storage for a torrent, used in + // disk_interface + 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 info_hash; + }; +} + +#endif diff --git a/include/libtorrent/string_util.hpp b/include/libtorrent/string_util.hpp new file mode 100644 index 0000000..953c724 --- /dev/null +++ b/include/libtorrent/string_util.hpp @@ -0,0 +1,153 @@ +/* + +Copyright (c) 2012, 2014-2020, Arvid Norberg +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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); + + // same as split_string, but if one sub-string starts with a double quote + // (") separators are ignored until the end double-quote. Unless if the + // separator itself is a double quote. + TORRENT_EXTRA_EXPORT std::pair split_string_quotes( + string_view last, char const sep); + + // removes whitespaces at the beginning of the string, in-place + TORRENT_EXTRA_EXPORT void ltrim(std::string& s); + +#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..5460134 --- /dev/null +++ b/include/libtorrent/string_view.hpp @@ -0,0 +1,109 @@ +/* + +Copyright (c) 2016-2017, 2019-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_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..a0b8d3e --- /dev/null +++ b/include/libtorrent/tailqueue.hpp @@ -0,0 +1,194 @@ +/* + +Copyright (c) 2010, 2013-2017, 2019, Arvid Norberg +Copyright (c) 2017, 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_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(tailqueue&& t): m_first(t.m_first), m_last(t.m_last), m_size(t.m_size) + { + t.m_first = nullptr; + t.m_last = nullptr; + t.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..7cf3880 --- /dev/null +++ b/include/libtorrent/time.hpp @@ -0,0 +1,92 @@ +/* + +Copyright (c) 2007, 2009, 2014-2017, 2019-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_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 milliseconds32 = std::chrono::duration>; + 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/torrent.hpp b/include/libtorrent/torrent.hpp new file mode 100644 index 0000000..a783112 --- /dev/null +++ b/include/libtorrent/torrent.hpp @@ -0,0 +1,1798 @@ +/* + +Copyright (c) 2003-2020, Arvid Norberg +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2020, Alden Torres +Copyright (c) 2017, Falcosc +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2017, AllSeeingEyeTolledEweSew +Copyright (c) 2018, d-komarov +Copyright (c) 2018-2019, Steven Siloti +Copyright (c) 2019, ghbplayer +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#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/hash_picker.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/bandwidth_limit.hpp" +#include "libtorrent/aux_/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/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/aux_/announce_entry.hpp" +#include "libtorrent/extensions.hpp" // for add_peer_flags_t +#include "libtorrent/ssl.hpp" + +#ifdef TORRENT_SSL_PEERS +// 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; + struct 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; + + // this is set to true when this web seed was created from a redirect + // from a global IP, and SSRF mitigation is enabled. It prevents this + // web seed from resolving to any local network IPs. + bool no_local_ips = 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); + no_local_ips = std::move(rhs.no_local_ips); + 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; + + std::unique_ptr m_hash_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; + + // This is the sum of all non-pad file sizes. In the next major version + // this is stored in file_storage and no longer need to be kept here. + std::int64_t m_size_on_disk = 0; + + // 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 haven'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 + struct TORRENT_EXTRA_EXPORT torrent + : private single_threaded + , private torrent_hot_members + , request_callback + , peer_class_set + , std::enable_shared_from_this + { + // add_torrent_params may contain large merkle trees that are best + // moved. Deleting the const& overload ensures that it's always moved in. + torrent(aux::session_interface& ses, bool session_paused, add_torrent_params&& p); + torrent(aux::session_interface&, bool, add_torrent_params const& p) = delete; + ~torrent() override; + + // This may be called from multiple threads + info_hash_t 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(); + } + + // 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&, client_data_t)> const& ext + , client_data_t 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(); + + void load_merkle_trees(aux::vector, file_index_t> t + , aux::vector, file_index_t> mask + , aux::vector, file_index_t> verified); + + // 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(aux::vector block_hashes + , 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); + void on_disk_read_complete(disk_buffer_holder, storage_error const& + , peer_request const&, std::shared_ptr); + + 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, bool was_paused = false); + 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, file_progress_flags_t flags); + +#if TORRENT_ABI_VERSION == 1 + void use_interface(std::string net_interface); +#endif + + void connect_to_url_seed(std::list::iterator); + bool connect_to_peer(torrent_peer*, 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; + static constexpr web_seed_flag_t no_local_ips = 1_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& tracker_ips + , struct tracker_response const& resp) override; + void tracker_request_error(tracker_request const& r + , error_code const& ec, operation_t op, 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(event_t = event_t::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 + + aux::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); + + // process the v2 block hashes for a piece + boost::tribool on_blocks_hashed(piece_index_t piece + , span block_hashes); + + 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& + , std::vector
    const& + , int port + , protocol_version); + + // 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& + , std::vector
    const& + , int port + , std::list::iterator); + + 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(aux::vector block_hashes + , 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 + // for failures detected with v2 hashes the failing blocks(s) + // are specified in blocks + // *blocks must be sorted in acending order* + void piece_failed(piece_index_t index, std::vector blocks = std::vector()); + + // the peers in "peers" participated in sending a bad piece. If + // "known_bad_peer" is true, we know for sure the peers are guilty, + // otherwise only one may be guilty (meaning we can't unconditionally + // disconnect) + void penalize_peers(std::set const& peers + , piece_index_t index + , bool known_bad_peer); + + // this is the handler for hash failure piece synchronization + // i.e. resetting the piece + void on_piece_sync(piece_index_t piece, std::vector const& blocks); + + // 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; + aux::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; + } + + hash_picker& get_hash_picker() + { + TORRENT_ASSERT(m_hash_picker.get()); + return *m_hash_picker; + } + hash_picker const& get_hash_picker() const + { + TORRENT_ASSERT(m_hash_picker.get()); + return *m_hash_picker; + } + + void need_hash_picker(); + bool has_hash_picker() const + { + return m_hash_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; } + + torrent_info const& torrent_file() const + { return *m_torrent_file; } + + hash_request pick_hashes(peer_connection* peer); + std::vector get_hashes(hash_request const& req) const; + bool add_hashes(hash_request const& req, span hashes); + void hashes_rejected(hash_request const& req); + void verify_block_hashes(piece_index_t index); + + std::shared_ptr get_torrent_file() const; + + std::shared_ptr get_torrent_copy_with_hashes() const; + + std::vector> get_piece_layers() const; + + std::vector trackers() const; + + // 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(resume_data_flags_t const flags, add_torrent_params& ret) 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; } + + error_code initialize_merkle_trees(); + + // 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); + + 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); + + // 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_SSL_PEERS + 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); + 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); + + client_data_t get_userdata() const { return m_userdata; } + + static constexpr int no_gauge_state = 0xf; + + private: + + void on_exception(std::exception const& e); + void on_error(error_code const& ec); + + // 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 + , protocol_version v, std::vector const& peers); + void on_dht_announce_response(protocol_version v, 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; + + // the number of bytes of pad files + std::int64_t m_padding_bytes = 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_SSL_PEERS + std::unique_ptr m_ssl_ctx; + + bool verify_peer_cert(bool const preverified, 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; + +#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 + + // v2 merkle tree for each file + aux::vector m_merkle_trees; + + // 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; + + // 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 + info_hash_t 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 + time_point32 m_started = aux::time_now32(); + + // if we're a seed, this is the timestamp of when we became one + time_point32 m_became_seed = aux::time_now32(); + + // if we're finished, this is the 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::int64_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; + aux::handler_storage m_deferred_handler_storage; + + // 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 + // ============================== + + // if we're currently in upload-mode this is the time timestamp of when + // we entered it + 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; + + // 8 bits free + +// ---- + + // the number of unchoked peers in this torrent + unsigned int m_num_uploads:24; + + // 4 unused bits + + // when this is true, this torrent supports peer exchange + bool m_enable_pex: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; + + // this is set to true if all piece layers were successfully loaded and + // validated. Only for v2 torrents + // TODO: this member can probably be removed + bool m_v2_piece_layers_validated:1; + +// ---- + + // 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)}; + + // user data as passed in by add_torrent_params + client_data_t m_userdata; + +// ---- + + // 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..21356d8 --- /dev/null +++ b/include/libtorrent/torrent_flags.hpp @@ -0,0 +1,312 @@ +/* + +Copyright (c) 2017-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_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. + TORRENT_DEPRECATED constexpr torrent_flags_t 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. + TORRENT_DEPRECATED constexpr torrent_flags_t 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. + TORRENT_DEPRECATED constexpr torrent_flags_t 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. + TORRENT_DEPRECATED constexpr torrent_flags_t 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. + TORRENT_DEPRECATED constexpr torrent_flags_t 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; + + // if this flag is set, the resume data will be assumed to be correct + // without validating it against any files on disk. This may be used when + // restoring a session by loading resume data from disk. It will save time + // and also delay any hard disk errors until files are actually needed. If + // the resume data cannot be trusted, or if a torrent is added for the first + // time to some save path that may already have some of the files, this flag + // should not be set. + constexpr torrent_flags_t no_verify_files = 22_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..4640548 --- /dev/null +++ b/include/libtorrent/torrent_handle.hpp @@ -0,0 +1,1359 @@ +/* + +Copyright (c) 2019, Amir Abrams +Copyright (c) 2003-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2015, 2018, Steven Siloti +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Falcosc +Copyright (c) 2017, 2020, AllSeeingEyeTolledEweSew +Copyright (c) 2019, ghbplayer +Copyright (c) 2019, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/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/torrent_info.hpp" +#include "libtorrent/peer_info.hpp" // for peer_source_flags_t +#include "libtorrent/download_priority.hpp" +#include "libtorrent/pex_flags.hpp" +#include "libtorrent/client_data.hpp" +#include "libtorrent/address.hpp" // for address_v4 and address_v6 + +namespace libtorrent { +namespace aux { + struct session_impl; +} + +#if TORRENT_ABI_VERSION == 1 + struct peer_list_entry; +#endif + struct torrent; + struct client_data_t; + +#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; + using file_progress_flags_t = flags::bitfield_flag; + + // 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); + tcp::endpoint peer() const; + + // 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 const* 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. + TORRENT_DEPRECATED state_t 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 struct 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. Using this + // flag when adding a piece that is actively being downloaded from other + // peers may have some unexpected consequences, as blocks currently + // being downloaded from peers may not be replaced. + 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()`` returns a vector with information about pieces + // that are partially downloaded or not downloaded but partially + // requested. See partial_piece_info for the fields in the returned + // vector. + std::vector get_download_queue() const; + 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 + +#if TORRENT_ABI_VERSION <= 2 + using file_progress_flags_t = libtorrent::file_progress_flags_t; +#endif + // 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. + static constexpr file_progress_flags_t piece_granularity = 0_bit; + + // This function fills in the supplied vector, or returns a 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 currently + // 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, file_progress_flags_t flags = {}) const; + std::vector file_progress(file_progress_flags_t flags = {}) 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&, client_data_t)> const& ext + , client_data_t userdata = client_data_t{}); + + // ``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 + // removed from the session AND destructed. + // + // To tell if the torrent_handle is in the session, use + // torrent_handle::in_session(). This will return true before + // session_handle::remove_torrent() is called, and false + // afterward. + // + // Clients should only use is_valid() to determine if the result of + // session::find_torrent() was successful. + // + // Unlike other member functions which return a value, is_valid() + // completes immediately, without blocking on a result from the + // network thread. Also unlike other functions, it never throws + // the system_error exception. + 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. + // + // For possible values of the ``flags`` parameter, see pause_flags_t. + // + // To know if a torrent is paused or not, call + // ``torrent_handle::flags()`` and check for the + // ``torrent_status::paused`` flag. + // + // .. 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) try + // { + // torrent_status s = h.status(); + // if (!s.has_metadata || !s.need_save_resume_data()) continue; + // + // h.save_resume_data(); + // ++outstanding_resume_data; + // } + // catch (lt::system_error const& e) + // { + // // the handle was invalid, ignore this one and move to the next + // } + // + // 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); + + // torrent_file() 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. + // + // Note that the torrent_info object returned here may be a different + // instance than the one added to the session, with different attributes + // like piece layers, dht nodes and trackers. A torrent_info object does + // not round-trip cleanly when added to a session. + // + // This means if you want to create a .torrent file by passing the + // torrent_info object into create_torrent, you need to use + // torrent_file_with_hashes() instead. + // + // torrent_file_with_hashes() returns a *copy* of the internal + // torrent_info and piece layer hashes (if it's a v2 torrent). The piece + // layers will only be included if they are available. If this torrent + // was added from a .torrent file with piece layers or if it's seeding, + // the piece layers are available. This function is more expensive than + // torrent_file() since it needs to make copies of this information. + // + // When constructing a create_torrent object from a torrent_info that's + // in a session, you need to use this function. + // + // Note that a torrent added from a magnet link may not have the full + // merkle trees for all files, and hence not have the complete piece + // layers. In that state, you cannot create a .torrent file even from + // the torrent_info returned from torrent_file_with_hashes(). Once the + // torrent completes downloading all files, becoming a seed, you can + // make a .torrent file from it. + std::shared_ptr torrent_file() const; + std::shared_ptr torrent_file_with_hashes() const; + + // returns the piece layers for all files in the torrent. If this is a + // v1 torrent (and doesn't have any piece layers) it returns an empty + // vector. This is a blocking call that will synchronize with the + // libtorrent network thread. + std::vector> piece_layers() 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 flags() instead + 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. + // To synchronize with the priorities taking effect, wait for the + // file_prio_alert. + // + // 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. + // + // Moving data from a file into the part file is currently not + // supported. If a file has its priority set to 0 *after* it has already + // been created, it will not be moved into the partfile. + 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. + // + // ``force_lsd_announce`` will announce the torrent on LSD + // immediately. + void force_reannounce(int seconds = 0, int idx = -1, reannounce_flags_t = {}) const; + void force_dht_announce() const; + void force_lsd_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. + // + // When copying files, sparse regions are not likely to be preserved. + // This makes it proportionally more expensive to move a large torrent + // when only few pieces have been downloaded, since the files are then + // allocated with zeros in the destination directory. + 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 + // 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 + + // returns the info-hash(es) 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. + // The ``info_hash()`` returns the SHA-1 info-hash for v1 torrents and a + // truncated hash for v2 torrents. For the full v2 info-hash, use + // ``info_hashes()`` instead. + sha1_hash info_hash() const; + info_hash_t info_hashes() 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; + + // returns the userdata pointer as set in add_torrent_params + client_data_t userdata() const; + + // Returns true if the torrent is in the session. It returns true before + // session::remove_torrent() is called, and false afterward. + // + // Note that this is a blocking function, unlike torrent_handle::is_valid() + // which returns immediately. + bool in_session() 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..3d63d6a --- /dev/null +++ b/include/libtorrent/torrent_info.hpp @@ -0,0 +1,775 @@ +/* + +Copyright (c) 2003-2011, 2013-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2016, Markus +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2017-2019, Steven Siloti +Copyright (c) 2017, 2019, Andrei Kurushin +Copyright (c) 2019, Amir Abrams +Copyright (c) 2020, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/info_hash.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/aux_/merkle_tree.hpp" + +namespace libtorrent { + + struct invariant_access; + +namespace aux { + + // 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 url_, type_t type_ + , std::string auth_ = std::string() + , headers_t 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; + }; + + using torrent_info_flags_t = flags::bitfield_flag; + +TORRENT_VERSION_NAMESPACE_3 + + // 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(info_hash_t 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) {} +#endif // TORRENT_ABI_VERSION + + // frees all storage associated with this torrent_info object + ~torrent_info(); + + // hidden + torrent_info& operator=(torrent_info const&) = delete; + torrent_info& operator=(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; + + // 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); + + // .. warning:: + // Using `remap_files()` is discouraged as it's incompatible with v2 + // torrents. This is because the piece boundaries and piece hashes in + // v2 torrents are intimately tied to the file boundaries. Instead, + // just rename individual files, or implement a custom disk_interface + // to customize how to store files. + // + // 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. + // + // ``trackers()`` returns all entries from announce-list. + // + // ``clear_trackers()`` removes all trackers from announce-list. + 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; } + void clear_trackers(); + + // 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. + 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; +#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. + // + // ``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& ext_auth = std::string() + , web_seed_entry::headers_t const& ext_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()`` returns the total number of bytes the torrent-file + // represents. Note that this is the number of pieces times the piece + // size (modulo the last piece possibly being smaller). With pad files, + // the total size will be larger than the sum of all (regular) file + // sizes. + std::int64_t total_size() const { return m_files.total_size(); } + + // ``piece_length()`` and ``num_pieces()`` returns 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. + 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. For BitTorrent v2 support, use + // ``info_hashes()`` to get an object that may hold both a v1 and v2 + // info-hash + sha1_hash info_hash() const noexcept; + info_hash_t const& info_hashes() const { return m_info_hash; } + + // returns whether this torrent has v1 and/or v2 metadata, respectively. + // Hybrid torrents have both. These are shortcuts for + // info_hashes().has_v1() and info_hashes().has_v2() calls. + bool v1() const; + bool v2() const; + +#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); } + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + TORRENT_DEPRECATED + file_entry file_at(int index) const { return m_files.at_deprecated(index); } + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#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 bool(m_flags & private_torrent); } + + // 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 bool(m_flags & i2p); } + + // internal + bool v2_piece_hashes_verified() const { return bool(m_flags & v2_has_piece_hashes); } + void set_piece_layers(aux::vector, file_index_t> pl); + + // 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_PRECOND(index >= piece_index_t(0)); + TORRENT_ASSERT_PRECOND(index < m_files.end_piece()); + TORRENT_ASSERT(is_loaded()); + int const idx = static_cast(index); + TORRENT_ASSERT(m_piece_hashes > 0); + TORRENT_ASSERT(m_piece_hashes < m_info_section_size); + TORRENT_ASSERT(idx < int((m_info_section_size - m_piece_hashes) / 20)); + return &m_info_section[std::ptrdiff_t(m_piece_hashes) + idx * 20]; + } + + bool is_loaded() const { return m_files.num_files() > 0; } + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // ``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. + TORRENT_DEPRECATED + std::vector const& merkle_tree() const { return m_merkle_tree; } + TORRENT_DEPRECATED + void set_merkle_tree(std::vector& h) + { TORRENT_ASSERT(h.size() == m_merkle_tree.size() ); m_merkle_tree.swap(h); } +#endif + + // ``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, 0 is + // returned. + // .. _`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 `max_pieces` parameter allows limiting the amount of memory + // dedicated to loading the torrent, and fails for torrents that exceed + // the limit. To load large torrents, this limit may also need to be + // raised in settings_pack::max_piece_count and in calls to + // read_resume_data(). + bool parse_info_section(bdecode_node const& info, error_code& ec, int max_pieces); + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED + bool parse_info_section(bdecode_node const& info, error_code& ec); +#endif + + // 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; + + // returns a the raw info section of the torrent file. + // The underlying buffer is still owned by the torrent_info object + span info_section() const + { return span(m_info_section.get(), m_info_section_size); } + +#if TORRENT_ABI_VERSION <= 2 + // swap the content of this and ``ti``. + TORRENT_DEPRECATED + 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()``. + // Even though the bytes returned by ``metadata()`` are not ``const``, + // they must not be modified. + TORRENT_DEPRECATED + int metadata_size() const { return m_info_section_size; } + TORRENT_DEPRECATED + boost::shared_array metadata() const; +#endif + + // return the bytes of the piece layer hashes for the specified file. If + // the file doesn't have a piece layer, an empty span is returned. + // The span size is divisible by 32, the size of a SHA-256 hash. + // If the size of the file is smaller than or equal to the piece size, + // the files "root hash" is the hash of the file and is not saved + // separately in the "piece layers" field, but this function still + // returns the root hash of the file in that case. + span piece_layer(file_index_t) const; + + // clears the piece layers from the torrent_info. This is done by the + // session when a torrent is added, to avoid storing it twice. The piece + // layer (or other hashes part of the merkle tree) are stored in the + // internal torrent object. + void free_piece_layers(); + + // internal + void internal_set_creator(string_view); + void internal_set_creation_date(std::time_t); + void internal_set_comment(string_view); + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // internal + TORRENT_DEPRECATED + bool add_merkle_nodes(std::map const& + , piece_index_t) { return false; } + TORRENT_DEPRECATED + std::map build_merkle_list(piece_index_t) const + { + return std::map(); + } + + // returns whether or not this is a merkle torrent. + // see `BEP 30`__. + // + // __ https://www.bittorrent.org/beps/bep_0030.html + TORRENT_DEPRECATED + bool is_merkle_torrent() const { return !m_merkle_tree.empty(); } +#endif + + private: + + // populate the piece layers from the metadata + bool parse_piece_layers(bdecode_node const& e, error_code& ec); + + bool parse_torrent_file(bdecode_node const& torrent_file, 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 struct ::lt::invariant_access; + void check_invariant() const; +#endif + + 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. These are offsets + // into the info dict 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 + // first value is the offset into the metadata where the string is, the + // second value 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 TORRENT_ABI_VERSION <= 2 + // 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; +#endif + + // v2 merkle tree for each file + // the actual hash buffers are always divisible by 32 (sha256_hash::size()) + aux::vector, file_index_t> m_piece_layers; + + // 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 + // it is used as if immutable, it cannot be const for technical reasons + // right now. + boost::shared_array m_info_section; + + // 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(es) that identify this torrent + info_hash_t m_info_hash; + + // this is the offset into the m_info_section buffer to the first byte of + // the first SHA-1 hash + std::int32_t m_piece_hashes = 0; + + // the number of bytes in m_info_section + std::int32_t m_info_section_size = 0; + + // 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. + static constexpr torrent_info_flags_t multifile = 0_bit; + + // this is true if the torrent is private. i.e., is should not + // be announced on the dht + static constexpr torrent_info_flags_t private_torrent = 1_bit; + + // 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) + static constexpr torrent_info_flags_t i2p = 2_bit; + + // this flag is set if we found an ssl-cert field in the info + // dictionary + static constexpr torrent_info_flags_t ssl_torrent = 3_bit; + + // v2 piece hashes were loaded from the torrent file and verified + static constexpr torrent_info_flags_t v2_has_piece_hashes = 4_bit; + + torrent_info_flags_t m_flags{}; + }; + +TORRENT_VERSION_NAMESPACE_3_END + +} + +#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..aae9680 --- /dev/null +++ b/include/libtorrent/torrent_peer.hpp @@ -0,0 +1,287 @@ +/* + +Copyright (c) 2014-2017, 2019, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2018, 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_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/info_hash.hpp" +#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 + + protocol_version protocol() { return protocol_v2 ? protocol_version::V2 : protocol_version::V1; } + + // 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. + std::uint32_t 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 + std::uint32_t optimistically_unchoked:1; + + // this is true if the torrent_peer is a seed, and we know for sure + // because we have connected to it and it told us it was a seed + std::uint32_t seed:1; + + // we've been told that this peer is upload-only, but we don't know for + // sure because we haven't connected to it yet. If we are finished, we + // will de-prioritize peers that may be seeds + std::uint32_t maybe_upload_only: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; + // this peer supports protocol version 2 + bool protocol_v2:1; +#if TORRENT_USE_ASSERTS + bool in_use = true; +#endif + }; + + struct TORRENT_EXTRA_EXPORT ipv4_peer : torrent_peer + { + ipv4_peer(tcp::endpoint const& ep, 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 dest, 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& ep, 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..53cc35b --- /dev/null +++ b/include/libtorrent/torrent_peer_allocator.hpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2010, 2013-2017, 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_PEER_ALLOCATOR_HPP_INCLUDED +#define TORRENT_PEER_ALLOCATOR_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/aux_/pool.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 + + aux::pool m_ipv4_peer_pool{sizeof(libtorrent::ipv4_peer), 500}; + aux::pool m_ipv6_peer_pool{sizeof(libtorrent::ipv6_peer), 500}; +#if TORRENT_USE_I2P + aux::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..aebd713 --- /dev/null +++ b/include/libtorrent/torrent_status.hpp @@ -0,0 +1,608 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, AllSeeingEyeTolledEweSew +Copyright (c) 2018, 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_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 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" +#endif + +TORRENT_VERSION_NAMESPACE_3 + + // 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 +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#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. +#if TORRENT_ABI_VERSION == 1 + allocating TORRENT_DEPRECATED_ENUM, +#else + unused_enum_for_backwards_compatibility_allocating, +#endif + + // 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 + TORRENT_DEPRECATED std::string 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 metadata for the torrent + static constexpr file_index_t error_file_metadata{-4}; + + // 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. + TORRENT_DEPRECATED time_duration 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). + // Once a torrent becomes seed, any piece- and file priorities are + // forgotten and all bytes are considered "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. + // Once a torrent becomes seed, any piece- and file priorities are + // forgotten and all bytes are considered "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) + TORRENT_DEPRECATED int time_since_upload = 0; + TORRENT_DEPRECATED int 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. + TORRENT_DEPRECATED int active_time = 0; + TORRENT_DEPRECATED int finished_time = 0; + TORRENT_DEPRECATED int 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. + TORRENT_DEPRECATED int last_scrape = 0; + + // the priority of this torrent + TORRENT_DEPRECATED int 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. + TORRENT_DEPRECATED bool 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. + TORRENT_DEPRECATED bool upload_mode = false; + + // true if the torrent is currently in share-mode, i.e. not downloading + // the torrent, but just helping the swarm out. + TORRENT_DEPRECATED bool share_mode = false; + + // true if the torrent is in super seeding mode + TORRENT_DEPRECATED bool 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()``. + TORRENT_DEPRECATED bool 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_ + TORRENT_DEPRECATED bool auto_managed = false; + + // true when the torrent is in sequential download mode. In this mode + // pieces are downloaded in order rather than rarest first. + TORRENT_DEPRECATED bool 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. + TORRENT_DEPRECATED bool 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. + TORRENT_DEPRECATED bool 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(). + TORRENT_DEPRECATED bool stop_when_ready = false; +#endif + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + + // the info-hash for this torrent + info_hash_t info_hashes; + + // 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_3_END +} // namespace libtorrent + +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..d077a80 --- /dev/null +++ b/include/libtorrent/tracker_manager.hpp @@ -0,0 +1,412 @@ +/* + +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2004-2020, Arvid Norberg +Copyright (c) 2016-2017, Steven Siloti +Copyright (c) 2016, 2020, Alden Torres +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 + +#include "libtorrent/flags.hpp" +#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_context.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" +#include "libtorrent/ssl.hpp" + +namespace libtorrent { + + class tracker_manager; + struct timeout_handler; + class udp_tracker_connection; + class http_tracker_connection; + struct counters; +#if TORRENT_USE_I2P + class i2p_connection; +#endif +namespace aux { + struct session_logger; + struct session_settings; + struct resolver_interface; +} + +using tracker_request_flags_t = flags::bitfield_flag; + +// Kinds of tracker announces. This is typically indicated as the ``&event=`` +// HTTP query string parameter to HTTP trackers. +enum class event_t : std::uint8_t +{ + none, + completed, + started, + stopped, + paused +}; + + struct TORRENT_EXTRA_EXPORT tracker_request + { + tracker_request() = default; + + static constexpr tracker_request_flags_t scrape_request = 0_bit; + + // affects interpretation of peers string in HTTP response + // see parse_tracker_response() + static constexpr tracker_request_flags_t i2p = 1_bit; + + std::string url; + std::string trackerid; +#if TORRENT_ABI_VERSION == 1 + std::string auth; +#endif + + std::shared_ptr filter; + + std::int64_t downloaded = -1; + std::int64_t uploaded = -1; + std::int64_t left = -1; + std::int64_t corrupt = 0; + std::int64_t redundant = 0; + std::uint16_t listen_port = 0; + event_t event = event_t::none; + tracker_request_flags_t kind = {}; + + std::uint32_t key = 0; + int num_want = 0; + 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 = false; + + // this is set to true if this request was triggered by a "manual" call to + // scrape_tracker() or force_reannounce() + bool triggered_manually = false; + +#if TORRENT_USE_SSL + 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 + , operation_t op + , 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_context&); + + 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(); + + auto get_executor() { return m_timeout.get_executor(); } + + 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 req + , io_context& 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, operation_t op, 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, operation_t op, 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 send_fun + , send_fun_hostname_t send_fun_hostname + , counters& stats_counters + , aux::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_context& ios + , tracker_request&& r + , aux::session_settings const& sett + , std::weak_ptr c + = std::weak_ptr()); + void queue_request( + io_context& 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 stop(); + + 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; } + aux::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; + aux::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/truncate.hpp b/include/libtorrent/truncate.hpp new file mode 100644 index 0000000..0786c81 --- /dev/null +++ b/include/libtorrent/truncate.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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_TRUNCATE_HPP_INCLUDED +#define TORRENT_TRUNCATE_HPP_INCLUDED + +#include "libtorrent/fwd.hpp" +#include "libtorrent/error_code.hpp" +#include + +namespace libtorrent { + +// Truncates files larger than specified in the file_storage, saved under +// the specified save_path. +TORRENT_EXPORT void truncate_files(file_storage const& fs, std::string const& save_path, storage_error& ec); + +} + +#endif diff --git a/include/libtorrent/udp_socket.hpp b/include/libtorrent/udp_socket.hpp new file mode 100644 index 0000000..db145c3 --- /dev/null +++ b/include/libtorrent/udp_socket.hpp @@ -0,0 +1,173 @@ +/* + +Copyright (c) 2007-2020, Arvid Norberg +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016, 2020, 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_UDP_SOCKET_HPP_INCLUDED +#define TORRENT_UDP_SOCKET_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/io_context.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 "libtorrent/aux_/resolver_interface.hpp" + +#include +#include + +namespace libtorrent { + +namespace aux { struct alert_manager; } + struct socks5; + + using udp_send_flags_t = flags::bitfield_flag; + + class TORRENT_EXTRA_EXPORT udp_socket : single_threaded + { + public: + udp_socket(io_context& ios, aux::listen_socket_handle ls); + + // non-copyable + udp_socket(udp_socket const&) = delete; + udp_socket& operator=(udp_socket const&) = delete; + + 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; } + udp::socket::executor_type get_executor() { return m_socket.get_executor(); } + + template + void async_read(Handler&& h) + { + m_socket.async_wait(udp::socket::wait_read, std::forward(h)); + } + + template + void async_write(Handler&& h) + { + m_socket.async_wait(udp::socket::wait_write, 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, aux::alert_manager& alerts + , aux::resolver_interface& resolver, bool send_local_ep); + 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: + + 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; + + io_context& m_ioc; + + 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; + }; +} + +#endif diff --git a/include/libtorrent/udp_tracker_connection.hpp b/include/libtorrent/udp_tracker_connection.hpp new file mode 100644 index 0000000..90c950f --- /dev/null +++ b/include/libtorrent/udp_tracker_connection.hpp @@ -0,0 +1,133 @@ +/* + +Copyright (c) 2004-2008, 2010, 2012, 2014-2017, 2019-2020, Arvid Norberg +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_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_context& 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 + , operation_t op + , 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..2aa2ec6 --- /dev/null +++ b/include/libtorrent/union_endpoint.hpp @@ -0,0 +1,115 @@ +/* + +Copyright (c) 2010, 2013, 2015-2019, Arvid Norberg +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. + +*/ + +#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..6dd8cd5 --- /dev/null +++ b/include/libtorrent/units.hpp @@ -0,0 +1,186 @@ +/* + +Copyright (c) 2016-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2019, Steven Siloti +Copyright (c) 2020, Silver Zachara +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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; + +#if TORRENT_USE_IOSTREAM + template + struct type_to_print_as + { + using type = typename std::conditional::type; + }; +#endif + + + 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; + +#if TORRENT_USE_IOSTREAM + friend std::ostream& operator<<(std::ostream& os, strong_typedef val) + { return os << static_cast::type>(static_cast(val)); } +#endif + + 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;} + +} // 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. Any specific torrent + // file has a well defined and immutable file list, and a file index into it + // always refers to the same file. + 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..b0c6a76 --- /dev/null +++ b/include/libtorrent/upnp.hpp @@ -0,0 +1,388 @@ +/* + +Copyright (c) 2007-2010, 2013-2020, Arvid Norberg +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016-2017, 2020, Alden Torres +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/aux_/resolver.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/aux_/portmap.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/ssl.hpp" + +#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); +} // namespace upnp_errors + + // 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_context& ios + , aux::session_settings const& settings + , aux::portmap_callback& cb + , address_v4 listen_address + , address_v4 netmask + , std::string listen_device + , aux::listen_socket_handle ls); + ~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* fmt, ...) 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&&) noexcept; + 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::noexcept_movable> 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; + aux::noexcept_movable
    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_context& m_io_service; + + aux::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; + +#if TORRENT_USE_SSL + ssl::context m_ssl_ctx; +#endif + + aux::listen_socket_handle m_listen_handle; +}; + +} // namespace libtorrent + +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..9c0596e --- /dev/null +++ b/include/libtorrent/utf8.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2005, 2008-2009, 2013, 2016-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_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/vector_utils.hpp b/include/libtorrent/vector_utils.hpp new file mode 100644 index 0000000..01fc3bf --- /dev/null +++ b/include/libtorrent/vector_utils.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2010, 2013-2014, 2016, 2018-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_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..8f26104 --- /dev/null +++ b/include/libtorrent/version.hpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2004, 2006, 2010, 2012, 2015, 2017-2020, Arvid Norberg +Copyright (c) 2016, Jan Berkel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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" +#include + +#define LIBTORRENT_VERSION_MAJOR 2 +#define LIBTORRENT_VERSION_MINOR 0 +#define LIBTORRENT_VERSION_TINY 6 + +// 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 "2.0.6.0" +#define LIBTORRENT_REVISION "7cf79a8d1" + +namespace libtorrent { + + // the major, minor and tiny versions of libtorrent + constexpr int version_major = 2; + constexpr int version_minor = 0; + constexpr int version_tiny = 6; + + // the libtorrent version in string form + constexpr char const* version_str = "2.0.6.0"; + + // the git commit of this libtorrent version + constexpr std::uint64_t version_revision = 0x7cf79a8d1; + + // 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..99eb566 --- /dev/null +++ b/include/libtorrent/web_connection_base.hpp @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2010, 2012, 2014-2020, Arvid Norberg +Copyright (c) 2016, 2019, 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 WEB_CONNECTION_BASE_HPP_INCLUDED +#define WEB_CONNECTION_BASE_HPP_INCLUDED + +#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_EXTRA_EXPORT web_connection_base + : public peer_connection + { + friend struct 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& pack + , web_seed_t const& 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..b6973ff --- /dev/null +++ b/include/libtorrent/web_peer_connection.hpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2006-2007, 2009-2020, Arvid Norberg +Copyright (c) 2016-2017, 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_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 struct 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& 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..832bff9 --- /dev/null +++ b/include/libtorrent/write_resume_data.hpp @@ -0,0 +1,58 @@ +/* + +Copyright (c) 2017-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_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); + + // writes only the fields to create a .torrent file. This function may fail + // with a ``std::system_error`` exception if: + // + // * The add_torrent_params object passed to this function does not contain the + // info dictionary (the ``ti`` field) + // * The piece layers are not complete for all files that need them + TORRENT_EXPORT entry write_torrent_file(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..014dc01 --- /dev/null +++ b/include/libtorrent/xml_parse.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2007, 2011-2017, 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_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/project-config.jam b/project-config.jam new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..517b029 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,89 @@ +[project] +requires-python = ">=3.7" + +[tool.cibuildwheel] +skip = "pp*" + +[tool.cibuildwheel.macos] +before-all = [ + "./tools/cibuildwheel/setup_boost.sh $BOOST_VERSION $BOOST_ROOT", + "brew install openssl", +] +test-command = [ + "cd {project}/bindings/python", + "python test.py", +] + +[tool.cibuildwheel.macos.environment] +BOOST_BUILD_PATH = "/tmp/boost/tools/build" +BOOST_ROOT = "/tmp/boost" +BOOST_VERSION = "1.76.0" +MACOSX_DEPLOYMENT_TARGET = "10.9" # required for full C++11 support +PATH = "/tmp/boost:$PATH" + +[[tool.cibuildwheel.overrides]] +before-all = [ + "./tools/cibuildwheel/setup_boost.sh $BOOST_VERSION $BOOST_ROOT", + "yum install -y glibc-static", # needed for libutil.a and libdl.a + "./tools/cibuildwheel/setup_ccache_on_manylinux.sh", + "./tools/cibuildwheel/setup_openssl.sh", +] +before-test = "ccache -s" +select = "*-manylinux_*" +test-command = [ + "cd {project}/bindings/python", + "python test.py", +] + +[tool.cibuildwheel.overrides.environment] # sub-table of previous block! +BOOST_BUILD_PATH = "/tmp/boost/tools/build" +BOOST_ROOT = "/tmp/boost" +BOOST_VERSION = "1.76.0" +PATH = "/usr/local/ccache/bin:/tmp/boost:$PATH" + +[[tool.cibuildwheel.overrides]] +before-all = [ + "./tools/cibuildwheel/setup_boost.sh $BOOST_VERSION $BOOST_ROOT", + "apk add ccache openssl-dev openssl-libs-static", + "./tools/cibuildwheel/setup_openssl.sh", +] +before-test = "ccache -s" +select = "*-musllinux_*" +test-command = [ + "cd {project}/bindings/python", + "python test.py", +] + +[tool.cibuildwheel.overrides.environment] # sub-table of previous block! +BOOST_BUILD_PATH = "/tmp/boost/tools/build" +BOOST_ROOT = "/tmp/boost" +BOOST_VERSION = "1.76.0" +PATH = "/usr/lib/ccache/bin:/tmp/boost:$PATH" + +[[tool.cibuildwheel.overrides]] +before-all = [ + "bash -c './tools/cibuildwheel/setup_boost.sh $BOOST_VERSION $BOOST_ROOT'", + "bash -c 'choco install --no-progress --x86 openssl'", # choco only allows EITHER 32 OR 64-bit version of a package +] +select = "*-win32" + +[[tool.cibuildwheel.overrides]] +before-all = [ + "bash -c './tools/cibuildwheel/setup_boost.sh $BOOST_VERSION $BOOST_ROOT'", + "bash -c 'choco install --no-progress openssl'", # choco only allows EITHER 32 OR 64-bit version of a package +] +select = "*-win_amd64" + +[tool.cibuildwheel.windows] +test-command = '''bash -c "cd '{project}/bindings/python' && python test.py"''' + +[tool.cibuildwheel.windows.environment] +BOOST_BUILD_PATH = 'c:/boost/tools/build' +BOOST_ROOT = 'c:/boost' +BOOST_VERSION = "1.76.0" +PATH = 'c:/boost;$PATH' + +[tool.isort] +profile = "google" +single_line_exclusions = [] +src_paths = ["."] diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..f3187b5 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,15 @@ +[flake8] +# https://black.readthedocs.io/en/stable/compatible_configs.html#flake8 +max-line-length = 88 +extend-ignore = E203, W503 + +[mypy] +warn_return_any = True +warn_redundant_casts = True +warn_unused_ignores = True +warn_unreachable = True +warn_unused_configs = True +#disallow_any_unimported = True +disallow_untyped_defs = True +disallow_incomplete_defs = True +mypy_path = bindings/python/install_data diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f833ba0 --- /dev/null +++ b/setup.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +import os +import runpy + +os.chdir('bindings/python') +runpy.run_path('setup.py') diff --git a/simulation/Jamfile b/simulation/Jamfile new file mode 100644 index 0000000..164e8b1 --- /dev/null +++ b/simulation/Jamfile @@ -0,0 +1,65 @@ +import testing ; +import feature : feature ; + +use-project /torrent : .. ; +use-project /libtorrent_test : ../test ; + +use-project /libsimulator : libsimulator ; + +project + : requirements + on + on + /torrent//torrent + /libtorrent_test//libtorrent_test + setup_swarm.cpp + setup_dht.cpp + create_torrent.cpp + utils.cpp + disk_io.cpp + transfer_sim.cpp + msvc:/wd4275 + msvc:/wd4005 + msvc:/wd4268 + : default-build + multi + full + on + on + 14 + built-in + ; + +run test_pause.cpp ; +run test_socks5.cpp ; +run test_checking.cpp ; +run test_optimistic_unchoke.cpp ; +run test_transfer.cpp ; +run test_transfer_matrix.cpp ; +run test_http_connection.cpp ; +run test_web_seed.cpp ; +run test_auto_manage.cpp ; +run test_torrent_status.cpp ; +run test_swarm.cpp ; +run test_session.cpp ; +run test_super_seeding.cpp ; +run test_utp.cpp ; +run test_dht.cpp ; +run test_dht_bootstrap.cpp ; +run test_dht_storage.cpp ; +run test_pe_crypto.cpp ; +run test_metadata_extension.cpp ; +run test_tracker.cpp ; +run test_thread_pool.cpp ; +run test_ip_filter.cpp ; +run test_dht_rate_limit.cpp ; +run test_fast_extensions.cpp ; +# TODO figure out what to do with this +# since v2 support was added re-mapped files are required to be piece aligned +# but test_file_pool requires mapping more files than there are pieces +#run test_file_pool.cpp ; +run test_save_resume.cpp ; +run test_error_handling.cpp ; +run test_timeout.cpp ; +run test_peer_connection.cpp ; + diff --git a/simulation/create_torrent.cpp b/simulation/create_torrent.cpp new file mode 100644 index 0000000..c5ebf67 --- /dev/null +++ b/simulation/create_torrent.cpp @@ -0,0 +1,73 @@ +/* + +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 "setup_transfer.hpp" // for ::create_torrent +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/create_torrent.hpp" +#include + + +std::string save_path(int idx) +{ + int const swarm_id = unit_test::test_counter(); + char path[200]; + std::snprintf(path, sizeof(path), "swarm-%04d-peer-%02d" + , swarm_id, idx); + return path; +} + +lt::add_torrent_params create_torrent(int const idx, bool const seed + , int const num_pieces, lt::create_flags_t const flags) +{ + // TODO: if we want non-seeding torrents, that could be a bit cheaper to + // create + lt::add_torrent_params params; + int swarm_id = unit_test::test_counter(); + char name[200]; + std::snprintf(name, sizeof(name), "temp-%02d", swarm_id); + std::string path = save_path(idx); + lt::error_code ec; + lt::create_directory(path, ec); + if (ec) std::printf("failed to create directory: \"%s\": %s\n" + , path.c_str(), ec.message().c_str()); + std::ofstream file(lt::combine_path(path, name).c_str()); + params.ti = ::create_torrent(&file, name, 0x4000, num_pieces + idx, false, flags); + file.close(); + + // by setting the save path to a dummy path, it won't be seeding + params.save_path = seed ? path : "dummy"; + return params; +} + + diff --git a/simulation/create_torrent.hpp b/simulation/create_torrent.hpp new file mode 100644 index 0000000..e7f70eb --- /dev/null +++ b/simulation/create_torrent.hpp @@ -0,0 +1,45 @@ +/* + +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_SIM_CREATE_TORRENT_HPP_INCLUDED +#define TORRENT_SIM_CREATE_TORRENT_HPP_INCLUDED + +#include +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/create_torrent.hpp" + +std::string save_path(int idx); +lt::add_torrent_params create_torrent(int idx, bool seed = true + , int num_pieces = 9, lt::create_flags_t flags = {}); + +#endif + diff --git a/simulation/disk_io.cpp b/simulation/disk_io.cpp new file mode 100644 index 0000000..460f093 --- /dev/null +++ b/simulation/disk_io.cpp @@ -0,0 +1,719 @@ +/* + +Copyright (c) 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "disk_io.hpp" +#include "utils.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/disk_observer.hpp" +#include "libtorrent/aux_/apply_pad_files.hpp" +#include "libtorrent/random.hpp" + +#include // for exchange() + +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 + +std::array generate_block_fill(lt::piece_index_t const p, int const block) +{ + int const v = (static_cast(p) << 8) | (block & 0xff); + std::array ret; + for (int i = 0; i < 0x4000; i += 4) + { + std::memcpy(ret.data() + i, reinterpret_cast(&v), 4); + } + return ret; +} + +lt::sha1_hash generate_hash1(lt::piece_index_t const p, lt::file_storage const& fs, int const pad_bytes) +{ + lt::hasher ret; + int const piece_size = fs.piece_size(p); + int const payload_size = piece_size - pad_bytes; + int offset = 0; + for (int block = 0; offset < payload_size; ++block) + { + auto const fill = generate_block_fill(p, block); + for (int i = 0; i < lt::default_block_size;) + { + int const bytes = std::min(int(fill.size()), payload_size - offset); + if (bytes <= 0) break; + ret.update(fill.data(), bytes); + offset += bytes; + i += bytes; + } + } + TORRENT_ASSERT(piece_size - offset == pad_bytes); + std::array const pad{{0, 0, 0, 0, 0, 0, 0, 0}}; + while (offset < piece_size) + { + int const bytes = std::min(int(pad.size()), piece_size - offset); + ret.update(pad.data(), bytes); + offset += bytes; + } + return ret.final(); +} + +lt::sha1_hash generate_hash2(lt::piece_index_t p, lt::file_storage const& fs + , lt::span const hashes, int const pad_bytes) +{ + int const piece_size = fs.piece_size(p); + int const payload_size = piece_size - pad_bytes; + int const piece_size2 = fs.piece_size2(p); + int const blocks_in_piece = (piece_size + lt::default_block_size - 1) / lt::default_block_size; + int const blocks_in_piece2 = fs.blocks_in_piece2(p); + TORRENT_ASSERT(int(hashes.size()) >= blocks_in_piece2); + int const blocks_to_read = std::max(blocks_in_piece, blocks_in_piece2); + + TORRENT_ASSERT(piece_size - pad_bytes == piece_size2); + + lt::hasher ret; + int offset = 0; + for (int block = 0; block < blocks_to_read; ++block) + { + lt::hasher256 v2_hash; + auto const fill = generate_block_fill(p, block); + + bool const v2 = piece_size2 - offset > 0; + + int const block_size = std::min(lt::default_block_size, payload_size - offset); + for (int i = 0; i < block_size;) + { + int const bytes = std::min(int(fill.size()), payload_size - offset); + TORRENT_ASSERT(bytes > 0); + ret.update(fill.data(), bytes); + + if (piece_size2 - offset > 0) + v2_hash.update(fill.data(), std::min(int(fill.size()), piece_size2 - offset)); + + offset += bytes; + i += bytes; + } + if (offset < piece_size) + { + std::vector padding(piece_size - offset, 0); + ret.update(padding); + } + if (v2) + hashes[block] = v2_hash.final(); + } + return ret.final(); +} + +lt::sha256_hash generate_block_hash(lt::piece_index_t p, int const offset) +{ + // TODO: this function is not correct for files whose size are not divisible + // by the block size (for the last block) + lt::hasher256 ret; + int const block = offset / lt::default_block_size; + auto const fill = generate_block_fill(p, block); + for (int i = 0; i < lt::default_block_size; i += fill.size()) + ret.update(fill); + return ret.final(); +} + +void generate_block(char* b, lt::peer_request const& r, int const pad_bytes) +{ + auto const fill = generate_block_fill(r.piece, (r.start / lt::default_block_size)); + + // for now we don't support unaligned start address + TORRENT_ASSERT((r.start % fill.size()) == 0); + char* end = b + r.length - pad_bytes; + while (b < end) + { + int const bytes = std::min(int(fill.size()), int(end - b)); + std::memcpy(b, fill.data(), bytes); + b += bytes; + } + + if (pad_bytes > 0) + std::memset(b, 0, pad_bytes); +} + +std::unordered_map compute_pad_bytes(lt::file_storage const& fs) +{ + std::unordered_map ret; + lt::aux::apply_pad_files(fs, [&](lt::piece_index_t p, int bytes) + { + ret.emplace(p, bytes); + }); + return ret; +} + +int pads_in_piece(std::unordered_map const& pb, lt::piece_index_t const p) +{ + auto it = pb.find(p); + return (it == pb.end()) ? 0 : it->second; +} + +int pads_in_req(std::unordered_map const& pb + , lt::peer_request const& r, int const piece_size) +{ + auto it = pb.find(r.piece); + if (it == pb.end()) return 0; + + int const pad_start = piece_size - it->second; + int const req_end = r.start + r.length; + return std::max(0, std::min(req_end - pad_start, r.length)); +} + +std::shared_ptr create_test_torrent(int const piece_size + , int const num_pieces, lt::create_flags_t const flags, int const num_files) +{ + lt::file_storage ifs; + int total_size = num_files * piece_size * num_pieces + 1234; + if (num_files == 1) + { + ifs.add_file("file-1", total_size); + } + else + { + int const file_size = total_size / num_files + 10; + for (int i = 0; i < num_files; ++i) + { + int const this_size = std::min(file_size, total_size); + ifs.add_file("test-torrent/file-" + std::to_string(i + 1), this_size); + total_size -= this_size; + } + } + lt::create_torrent t(ifs, piece_size, flags); + + lt::file_storage const& fs = t.files(); + + auto const pad_bytes = compute_pad_bytes(fs); + + if (flags & lt::create_torrent::v1_only) + { + for (auto const i : fs.piece_range()) + t.set_hash(i, generate_hash1(i, fs, pads_in_piece(pad_bytes, i))); + } + else + { + int const blocks_per_piece = piece_size / lt::default_block_size; + TORRENT_ASSERT(blocks_per_piece * lt::default_block_size == piece_size); + // blocks per piece must be a power of two + TORRENT_ASSERT((blocks_per_piece & (blocks_per_piece - 1)) == 0); + + lt::aux::vector blocks(blocks_per_piece); + std::vector scratch_space; + + for (auto const f : fs.file_range()) + { + if (fs.pad_file_at(f)) continue; + auto const file_piece = fs.piece_index_at_file(f); + for (auto const p : fs.file_piece_range(f)) + { + auto const piece = file_piece + p; + auto const blocks_in_piece = fs.blocks_in_piece2(piece); + TORRENT_ASSERT(blocks_in_piece > 0); + TORRENT_ASSERT(blocks_in_piece <= int(blocks.size())); + auto const hash = generate_hash2(piece, fs, blocks, pads_in_piece(pad_bytes, piece)); + auto const piece_layer_hash = lt::merkle_root_scratch( + lt::span(blocks).first(blocks_in_piece) + , blocks_per_piece + , {} + , scratch_space); + t.set_hash2(f, p, piece_layer_hash); + + if (!(flags & lt::create_torrent::v2_only)) + t.set_hash(file_piece + p, hash); + } + } + } + + lt::entry tor = t.generate(); + + std::vector tmp; + bencode(std::back_inserter(tmp), tor); + lt::error_code ec; + return std::make_shared(tmp, ec, lt::from_span); +} + +lt::add_torrent_params create_test_torrent( + int const num_pieces, lt::create_flags_t const flags, int const blocks_per_piece + , int const num_files) +{ + lt::add_torrent_params params; + params.ti = ::create_test_torrent(lt::default_block_size * blocks_per_piece, num_pieces, flags, num_files); + // this is unused by the test disk I/O + params.save_path = "."; + return params; +} + +// This is a disk subsystem used for tests (simulations specifically), it: +// +// * supports only a single torrent at a time (to keep it simple) +// * does not support arbitrary data, it generates the data read from it +// according to a specific function. This keeps the memory footprint down even +// for large tests +// * it can simulate delays in reading and writing +// * it can simulate disk full + +struct test_disk_io final : lt::disk_interface + , lt::buffer_allocator_interface +{ + explicit test_disk_io(lt::io_context& ioc, test_disk state) + : m_timer(ioc) + , m_state(state) + , m_ioc(ioc) + {} + + void settings_updated() override {} + + lt::storage_holder new_torrent(lt::storage_params const& params + , std::shared_ptr const&) override + { + TORRENT_ASSERT(m_files == nullptr); + // This test disk I/O system only supports a single torrent + // to keep it simple + lt::file_storage const& fs = params.files; + m_files = &fs; + m_blocks_per_piece = fs.piece_length() / lt::default_block_size; + m_have.resize(m_files->num_pieces() * m_blocks_per_piece, m_state.files == existing_files_mode::full_valid); + m_pad_bytes = compute_pad_bytes(fs); + + if (m_state.files == existing_files_mode::partial_valid) + { + // we have the first half of the blocks + for (std::size_t i = 0; i < m_have.size() / 2u; ++i) + m_have.set_bit(i); + } + + return lt::storage_holder(lt::storage_index_t{0}, *this); + } + + void remove_torrent(lt::storage_index_t const idx) override + { + TORRENT_ASSERT(static_cast(idx) == 0); + TORRENT_ASSERT(m_files != nullptr); + + queue_event(lt::microseconds(1), [this] () mutable { + m_files = nullptr; + m_blocks_per_piece = 0; + m_have.clear(); + }); + } + + void abort(bool) override {} + + void async_read(lt::storage_index_t const storage, lt::peer_request const& r + , std::function h + , lt::disk_job_flags_t) override + { + TORRENT_ASSERT(static_cast(storage) == 0); + TORRENT_ASSERT(m_files); + + // a real diskt I/O implementation would have to support this, but in + // the simulations, we never send unaligned piece requests. + TORRENT_ASSERT((r.start % lt::default_block_size) == 0); + TORRENT_ASSERT((r.length <= lt::default_block_size)); + + auto const seek_time = disk_seek(r.piece, r.start, lt::default_block_size); + + queue_event(seek_time + m_state.read_time, [this,r, h=std::move(h)] () mutable { + lt::disk_buffer_holder buf(*this, new char[lt::default_block_size], r.length); + + if (m_have.get_bit(block_index(r))) + { + if (m_state.corrupt_data_in-- <= 0) + lt::aux::random_bytes(buf); + else + generate_block(buf.data(), r, pads_in_req(m_pad_bytes, r, m_files->piece_size(r.piece))); + } + + post(m_ioc, [h=std::move(h), b=std::move(buf)] () mutable { h(std::move(b), lt::storage_error{}); }); + }); + } + + bool async_write(lt::storage_index_t storage, lt::peer_request const& r + , char const* buf, std::shared_ptr o + , std::function handler + , lt::disk_job_flags_t) override + { + TORRENT_ASSERT(m_files); + + if (m_state.space_left < lt::default_block_size) + { + queue_event(lt::milliseconds(1), [this,h=std::move(handler)] () mutable { + post(m_ioc, [h=std::move(h)] + { + h(lt::storage_error(lt::error_code(boost::system::errc::no_space_on_device, lt::generic_category()) + , lt::operation_t::file_write)); + }); + if (m_state.recover_full_disk) + m_state.space_left = std::numeric_limits::max(); + }); + + if (m_write_queue > m_state.high_watermark || m_exceeded_max_size) + { + m_observers.push_back(std::move(o)); + return true; + } + return false; + } + + bool const valid = validate_block(*m_files, buf, r); + + auto const seek_time = disk_seek(r.piece, r.start, lt::default_block_size); + + queue_event(seek_time + m_state.write_time, [this,valid,r,h=std::move(handler)] () mutable { + + if (valid) + { + m_have.set_bit(block_index(r)); + m_state.space_left -= lt::default_block_size; + } + + post(m_ioc, [h=std::move(h)]{ h(lt::storage_error()); }); + + TORRENT_ASSERT(m_write_queue > 0); + --m_write_queue; + check_buffer_level(); + }); + + m_write_queue += 1; + if (m_write_queue > m_state.high_watermark || m_exceeded_max_size) + { + m_exceeded_max_size = true; + m_observers.push_back(std::move(o)); + return true; + } + + return false; + } + + void async_hash(lt::storage_index_t storage, lt::piece_index_t const piece + , lt::span block_hashes, lt::disk_job_flags_t + , std::function handler) override + { + TORRENT_ASSERT(m_files); + + auto const seek_time = disk_seek(piece, 0, m_blocks_per_piece * lt::default_block_size); + + auto const delay = seek_time + + m_state.read_time * m_blocks_per_piece + + m_state.hash_time * m_blocks_per_piece + + m_state.hash_time * block_hashes.size(); + + queue_event(delay, [this, piece, block_hashes, h=std::move(handler)] () mutable { + + int const piece_size = m_files->piece_size(piece); + int const pad_bytes = pads_in_piece(m_pad_bytes, piece); + int const payload_blocks = piece_size / lt::default_block_size - pad_bytes / lt::default_block_size; + int const block_idx = piece * m_blocks_per_piece; + for (int i = 0; i < payload_blocks; ++i) + { + if (!m_have.get_bit(block_idx + i)) + { + lt::sha1_hash ph{}; + if (m_state.files == existing_files_mode::full_invalid) + { + for (auto& h : block_hashes) + h = rand_sha256(); + ph = rand_sha1(); + } + // If we're missing a block, return an invalid hash + post(m_ioc, [h=std::move(h), piece, ph]{ h(piece, ph, lt::storage_error{}); }); + return; + } + } + + lt::sha1_hash hash; + if (block_hashes.empty()) + hash = generate_hash1(piece, *m_files, pads_in_piece(m_pad_bytes, piece)); + else + hash = generate_hash2(piece, *m_files, block_hashes, pads_in_piece(m_pad_bytes, piece)); + post(m_ioc, [h=std::move(h), piece, hash]{ h(piece, hash, lt::storage_error{}); }); + }); + } + + void async_hash2(lt::storage_index_t storage, lt::piece_index_t const piece + , int const offset, lt::disk_job_flags_t + , std::function handler) override + { + TORRENT_ASSERT(m_files); + + auto const seek_time = disk_seek(piece, offset, m_blocks_per_piece * lt::default_block_size); + + auto const delay = seek_time + m_state.hash_time + m_state.read_time; + queue_event(delay, [this, piece, offset, h=std::move(handler)] () mutable { + int const block_idx = piece * m_blocks_per_piece + offset / lt::default_block_size; + lt::sha256_hash hash; + if (!m_have.get_bit(block_idx)) + { + if (m_state.files == existing_files_mode::full_invalid) + hash = rand_sha256(); + } + else + { + hash = generate_block_hash(piece, offset); + } + post(m_ioc, [h=std::move(h),piece, hash] { h(piece, hash, lt::storage_error{}); }); + }); + } + + void async_move_storage(lt::storage_index_t, std::string p, lt::move_flags_t + , std::function handler) override + { + TORRENT_ASSERT(m_files); + post(m_ioc, [=]{ + handler(lt::status_t::fatal_disk_error, p + , lt::storage_error(lt::error_code(boost::system::errc::operation_not_supported, lt::system_category()))); + }); + } + + void async_release_files(lt::storage_index_t, std::function f) override + { + TORRENT_ASSERT(m_files); + queue_event(lt::microseconds(1), std::move(f)); + } + + void async_delete_files(lt::storage_index_t, lt::remove_flags_t + , std::function handler) override + { + TORRENT_ASSERT(m_files); + + queue_event(lt::microseconds(1), [this,h=std::move(handler)] () mutable { + m_have.clear_all(); + post(m_ioc, [h=std::move(h)]{ h(lt::storage_error()); }); + }); + } + + void async_check_files(lt::storage_index_t + , lt::add_torrent_params const* p + , lt::aux::vector + , std::function handler) override + { + TORRENT_ASSERT(m_files); + + auto ret = lt::status_t::need_full_check; + if (p && p->flags & lt::torrent_flags::seed_mode) + ret = lt::status_t::no_error; + else if (m_state.files == existing_files_mode::no_files) + ret = lt::status_t::no_error; + + queue_event(lt::microseconds(1), [this,ret,h=std::move(handler)] () mutable { + post(m_ioc, [ret,h=std::move(h)] { h(ret, lt::storage_error()); }); + }); + } + + void async_rename_file(lt::storage_index_t + , lt::file_index_t const idx + , std::string const name + , std::function handler) override + { + TORRENT_ASSERT(m_files); + post(m_ioc, [=]{ handler(name, idx, lt::storage_error()); }); + } + + void async_stop_torrent(lt::storage_index_t, std::function handler) override + { + TORRENT_ASSERT(m_files); + post(m_ioc, handler); + } + + void async_set_file_priority(lt::storage_index_t + , lt::aux::vector prio + , std::function)> handler) override + { + TORRENT_ASSERT(m_files); + post(m_ioc, [=]{ + handler(lt::storage_error(lt::error_code( + boost::system::errc::operation_not_supported, lt::system_category())), std::move(prio)); + }); + } + + void async_clear_piece(lt::storage_index_t, lt::piece_index_t index + , std::function handler) override + { + TORRENT_ASSERT(m_files); + post(m_ioc, [=]{ handler(index); }); + } + + // implements buffer_allocator_interface + void free_disk_buffer(char* buf) override + { + delete[] buf; + } + + void update_stats_counters(lt::counters&) const override {} + + std::vector get_status(lt::storage_index_t) const override + { return {}; } + + void submit_jobs() override {} + +private: + + lt::time_duration disk_seek(lt::piece_index_t const piece, int start, int const size = lt::default_block_size) + { + std::int64_t const offset = std::int64_t(static_cast(piece)) * m_files->piece_length() + start; + return (std::exchange(m_last_disk_offset, offset + size) == offset) + ? lt::milliseconds(0) : m_state.seek_time; + } + + int block_index(lt::peer_request const& r) const + { + return r.piece * m_blocks_per_piece + r.start / lt::default_block_size; + } + + bool validate_block(lt::file_storage const& fs, char const* b, lt::peer_request const& r) const + { + auto const fill = generate_block_fill(r.piece, r.start / lt::default_block_size); + int const piece_size = fs.piece_size(r.piece); + int payload_bytes = (piece_size - pads_in_piece(m_pad_bytes, r.piece)) - r.start; + int offset = 0; + while (offset < r.length && payload_bytes > 0) + { + int const to_compare = std::min(payload_bytes, int(fill.size())); + if (std::memcmp(b, fill.data(), to_compare) != 0) return false; + b += to_compare; + offset += to_compare; + payload_bytes -= to_compare; + } + if (offset < r.length) + { + // the pad bytes must be zero + return std::all_of(b, b + r.length - offset, [](char const c) { return c == 0; }); + } + return true; + } + + void queue_event(lt::time_duration dt, std::function f) + { + if (m_event_queue.empty()) + { + m_event_queue.push_back({lt::clock_type::now() + dt, std::move(f)}); + m_timer.expires_after(dt); + using namespace std::placeholders; + m_timer.async_wait(std::bind(&test_disk_io::on_timer, this, _1)); + } + else + { + m_event_queue.push_back({m_event_queue.back().first + dt, std::move(f)}); + } + } + + void on_timer(lt::error_code const&) + { + if (m_event_queue.empty()) + return; + + { + auto f = std::move(m_event_queue.front().second); + m_event_queue.pop_front(); + if (f) f(); + } + + if (m_event_queue.empty()) + return; + + m_timer.expires_at(m_event_queue.back().first); + using namespace std::placeholders; + m_timer.async_wait(std::bind(&test_disk_io::on_timer, this, _1)); + } + + void check_buffer_level() + { + if (!m_exceeded_max_size || m_write_queue > m_state.low_watermark) return; + + m_exceeded_max_size = false; + + std::vector> cbs; + m_observers.swap(cbs); + post(m_ioc, std::bind(&watermark_callback, std::move(cbs))); + } + + std::vector> m_observers; + int m_write_queue = 0; + bool m_exceeded_max_size = false; + + // events that are supposed to trigger in the future are put in this queue + std::deque>> m_event_queue; + lt::deadline_timer m_timer; + + // the last read or write operation pushed at the end of the event queue. If + // the disk operation that's about to be pushed is immediately following + // this one, there is no seek delay + std::int64_t m_last_disk_offset = 0; + + test_disk m_state; + + // we only support a single torrent. This is set if it has been added + lt::file_storage const* m_files = nullptr; + + // marks blocks as they are written (as long as the correct block is written) + // when computing the hash of a piece where not all blocks are written, will + // fail + lt::bitfield m_have; + + int m_blocks_per_piece; + + // callbacks are posted on this + lt::io_context& m_ioc; + + std::unordered_map m_pad_bytes; +}; + +std::unique_ptr test_disk::operator()( + lt::io_context& ioc, lt::settings_interface const&, lt::counters&) +{ + return std::make_unique(ioc, *this); +} + + +std::ostream& operator<<(std::ostream& os, existing_files_mode const mode) +{ + switch (mode) + { + case existing_files_mode::no_files: return os << "no_files"; + case existing_files_mode::full_invalid: return os << "full_invalid"; + case existing_files_mode::partial_valid: return os << "partial_valid"; + case existing_files_mode::full_valid: return os << "full_valid"; + } + return os << ""; +} diff --git a/simulation/disk_io.hpp b/simulation/disk_io.hpp new file mode 100644 index 0000000..fe3a89a --- /dev/null +++ b/simulation/disk_io.hpp @@ -0,0 +1,131 @@ +/* + +Copyright (c) 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 SIMULATION_DISK_IO_HPP +#define SIMULATION_DISK_IO_HPP + +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/aux_/merkle.hpp" +#include "libtorrent/io_context.hpp" +#include "test_utils.hpp" + +#include +#include + +std::array generate_block_fill(lt::piece_index_t const p, int const block); +lt::sha1_hash generate_hash1(lt::piece_index_t const p, lt::file_storage const& fs); +lt::sha1_hash generate_hash2(lt::piece_index_t p, lt::file_storage const& fs + , lt::span const hashes); +lt::sha256_hash generate_block_hash(lt::piece_index_t p, int offset); +void generate_block(char* b, lt::peer_request const& r); +std::shared_ptr create_test_torrent(int piece_size + , int num_pieces, lt::create_flags_t flags, int num_files = 1); +lt::add_torrent_params create_test_torrent( + int num_pieces, lt::create_flags_t flags, int blocks_per_piece, int num_files = 1); + +enum class existing_files_mode : std::uint8_t +{ + no_files, full_invalid, partial_valid, full_valid +}; + +struct test_disk +{ + test_disk set_seed(bool const s = true) const + { + return set_files(s ? existing_files_mode::full_valid : existing_files_mode::no_files); + } + test_disk set_files(existing_files_mode const s) const + { + auto ret = *this; + ret.files = s; + return ret; + } + test_disk set_space_left(int const left) const + { + auto ret = *this; + ret.space_left = left; + return ret; + } + test_disk set_recover_full_disk() const + { + auto ret = *this; + ret.recover_full_disk = true; + return ret; + } + test_disk send_corrupt_data(int const blocks) const + { + auto ret = *this; + ret.corrupt_data_in = blocks; + return ret; + } + + // the number of blocks/write jobs in the queue before we exceed the write + // queue size. Once the level drops below the low watermark, we allow writes + // again + int high_watermark = 50; + int low_watermark = 40; + + std::unique_ptr operator()( + lt::io_context& ioc, lt::settings_interface const&, lt::counters&); + + // seek time in fron of every read and write + lt::time_duration seek_time = lt::milliseconds(10); + + // hash time per block + lt::time_duration hash_time = lt::microseconds(15); + + // write time per block + lt::time_duration write_time = lt::microseconds(2); + + // read time per block + lt::time_duration read_time = lt::microseconds(1); + + // when checking files, say we have some files on disk already (but not with + // valid data) + existing_files_mode files = existing_files_mode::no_files; + + // after having failed with disk-full error, reset space_left to int_max + bool recover_full_disk = false; + + // after sending this many blocks, send corrupt data + int corrupt_data_in = std::numeric_limits::max(); + + // after having written this many bytes, fail with disk-full + int space_left = std::numeric_limits::max(); +}; + +std::ostream& operator<<(std::ostream& os, existing_files_mode const mode); + +#endif diff --git a/simulation/fake_peer.hpp b/simulation/fake_peer.hpp new file mode 100644 index 0000000..981460b --- /dev/null +++ b/simulation/fake_peer.hpp @@ -0,0 +1,473 @@ +/* + +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 SIMULATION_FAKE_PEER_HPP +#define SIMULATION_FAKE_PEER_HPP + +#include +#include // for snprintf + +#include +#include "test.hpp" +#include "simulator/simulator.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/random.hpp" + +using namespace sim; + + +struct fake_peer +{ + fake_peer(simulation& sim, char const* ip) + : m_ioc(sim, asio::ip::make_address(ip)) + { + boost::system::error_code ec; + m_acceptor.open(asio::ip::tcp::v4(), ec); + TEST_CHECK(!ec); + m_acceptor.bind(asio::ip::tcp::endpoint(asio::ip::address_v4::any(), 6881), ec); + TEST_CHECK(!ec); + m_acceptor.listen(10, ec); + TEST_CHECK(!ec); + + m_acceptor.async_accept(m_socket, [&] (boost::system::error_code const& ec) + { + using namespace std::placeholders; + if (ec) return; + + asio::async_read(m_socket, asio::buffer(m_out_buffer.data(), 68) + , std::bind(&fake_peer::read_handshake, this, _1, _2)); + + m_accepted = true; + }); + } + + void close() + { + m_acceptor.close(); + m_socket.close(); + } + + void connect_to(asio::ip::tcp::endpoint ep, lt::sha1_hash const& ih) + { + using namespace std::placeholders; + + m_info_hash = ih; + + std::printf("fake_peer::connect_to(%s)\n", lt::print_endpoint(ep).c_str()); + m_socket.async_connect(ep, std::bind(&fake_peer::write_handshake + , this, _1, ih)); + } + + bool accepted() const { return m_accepted; } + bool connected() const { return m_connected; } + bool disconnected() const { return m_disconnected; } + + void send_request(lt::piece_index_t p, int block) + { + int const len = 4 + 1 + 4 * 3; + m_send_buffer.resize(m_send_buffer.size() + len); + char* ptr = m_send_buffer.data() + m_send_buffer.size() - len; + + lt::aux::write_uint32(len - 4, ptr); + lt::aux::write_uint8(6, ptr); + lt::aux::write_uint32(static_cast(p), ptr); + lt::aux::write_uint32(block * 0x4000, ptr); + lt::aux::write_uint32(0x4000, ptr); + } + + void send_keepalive() + { + int const len = 4; + m_send_buffer.resize(m_send_buffer.size() + len); + char* ptr = m_send_buffer.data() + m_send_buffer.size() - len; + + lt::aux::write_uint32(0, ptr); + } + + void send_bitfield(std::vector const& pieces) + { + int const bytes = (int(pieces.size()) + 7) / 8; + m_send_buffer.resize(m_send_buffer.size() + 5 + bytes); + char* ptr = m_send_buffer.data() + m_send_buffer.size() - 5 - bytes; + + lt::aux::write_uint32(1 + bytes, ptr); + lt::aux::write_uint8(5, ptr); + + std::uint8_t b = 0; + int cnt = 7; + for (auto i = pieces.begin(), end(pieces.end()); i != end; ++i) + { + if (*i) b |= 1 << cnt; + --cnt; + if (cnt < 0) + { + lt::aux::write_uint8(b, ptr); + b = 0; + cnt = 7; + } + } + if (cnt < 7) + lt::aux::write_uint8(b, ptr); + } + + void send_interested() { send_simple_msg(2); } + void send_not_interested() { send_simple_msg(3); } + void send_have_all() { send_simple_msg(0xe); } + void send_have_none() { send_simple_msg(0xf); } + void send_invalid_message() { send_simple_msg(0xff); } + void send_large_message() + { + m_send_buffer.resize(m_send_buffer.size() + 5); + char* ptr = m_send_buffer.data() + m_send_buffer.size() - 5; + + lt::aux::write_uint32(2000000, ptr); + lt::aux::write_uint8(0, ptr); + } + + void flush_send_buffer() + { + TORRENT_ASSERT(!m_writing); + m_writing = true; + asio::async_write(m_socket,asio::buffer(m_send_buffer) + , [this](boost::system::error_code const& ec , size_t len) + { + TORRENT_ASSERT(m_writing); + m_writing = false; + m_send_buffer.clear(); + }); + } + +private: + + void send_simple_msg(std::uint8_t const msg_code) + { + m_send_buffer.resize(m_send_buffer.size() + 5); + char* ptr = m_send_buffer.data() + m_send_buffer.size() - 5; + + lt::aux::write_uint32(1, ptr); + lt::aux::write_uint8(msg_code, ptr); + } + + void write_handshake(boost::system::error_code const& ec + , lt::sha1_hash ih) + { + using namespace std::placeholders; + + asio::ip::tcp::endpoint const ep = m_socket.remote_endpoint(); + std::printf("fake_peer::connect(%s) -> (%d) %s\n" + , lt::print_endpoint(ep).c_str() + , ec.value(), ec.message().c_str()); + if (ec) return; + + static char const handshake[] + = "\x13" "BitTorrent protocol\0\0\0\0\0\0\0\x04" + " " // space for info-hash + "aaaaaaaaaaaaaaaaaaaa"; // peer-id + int const len = sizeof(handshake) - 1; + memcpy(m_out_buffer.data(), handshake, len); + memcpy(&m_out_buffer[28], ih.data(), 20); + lt::aux::random_bytes({&m_out_buffer[48], 20}); + + TORRENT_ASSERT(!m_writing); + m_writing = true; + asio::async_write(m_socket, asio::buffer(m_out_buffer.data(), len) + , [this, ep](boost::system::error_code const& ec + , size_t /* bytes_transferred */) + { + TORRENT_ASSERT(m_writing); + m_writing = false; + std::printf("fake_peer::write_handshake(%s) -> (%d) %s\n" + , lt::print_endpoint(ep).c_str(), ec.value() + , ec.message().c_str()); + if (!m_send_buffer.empty()) + { + TORRENT_ASSERT(!m_writing); + m_writing = true; + asio::async_write(m_socket, asio::buffer(m_send_buffer) + , std::bind(&fake_peer::write_send_buffer, this, _1, _2)); + } + else + { + asio::async_read(m_socket, asio::buffer(m_out_buffer.data(), 68) + , std::bind(&fake_peer::read_handshake, this, _1, _2)); + } + }); + } + + void read_handshake(lt::error_code const& ec, size_t /* bytes_transferred */) + { + using namespace std::placeholders; + + std::printf("fake_peer::read_handshake -> (%d) %s\n" + , ec.value(), ec.message().c_str()); + if (ec) + { + m_socket.close(); + return; + } + + if (memcmp(m_out_buffer.data(), "\x13" "BitTorrent protocol", 20) != 0) + { + std::printf(" invalid protocol specifier\n"); + m_socket.close(); + return; + } + + // if this peer accepted an incoming connection, we don't know what the + // info hash is supposed to be + if (!m_info_hash.is_all_zeros() + && memcmp(&m_out_buffer[28], m_info_hash.data(), 20) != 0) + { + std::printf(" invalid info hash\n"); + m_socket.close(); + return; + } + + m_connected = true; + + // keep reading until we receie EOF, then set m_disconnected = true + m_socket.async_read_some(asio::buffer(m_out_buffer) + , std::bind(&fake_peer::on_read, this, _1, _2)); + } + + void on_read(lt::error_code const& ec, size_t bytes_transferred) + { + using namespace std::placeholders; + + std::printf("fake_peer::on_read(%d bytes) -> (%d) %s\n" + , int(bytes_transferred), ec.value(), ec.message().c_str()); + if (ec) + { + std::printf(" closing\n"); + m_disconnected = true; + m_socket.close(); + return; + } + + m_socket.async_read_some(asio::buffer(m_out_buffer) + , std::bind(&fake_peer::on_read, this, _1, _2)); + } + + void write_send_buffer(boost::system::error_code const& ec + , size_t /* bytes_transferred */) + { + using namespace std::placeholders; + + printf("fake_peer::write_send_buffer() -> (%d) %s\n" + , ec.value(), ec.message().c_str()); + + TORRENT_ASSERT(m_writing); + m_writing = false; + + m_send_buffer.clear(); + asio::async_read(m_socket, asio::buffer(m_out_buffer.data(), 68) + , std::bind(&fake_peer::read_handshake, this, _1, _2)); + } + + std::array m_out_buffer; + + asio::io_context m_ioc; + asio::ip::tcp::acceptor m_acceptor{m_ioc}; + asio::ip::tcp::socket m_socket{m_ioc}; + lt::sha1_hash m_info_hash; + + // set to true if this peer received an incoming connection + // if this is an outgoing connection, this will always be false + bool m_accepted = false; + + // set to true if this peer completed a bittorrent handshake + bool m_connected = false; + + // set to true if this peer has been disconnected by the other end + bool m_disconnected = false; + + // set to true while there's an outstanding asyn write operation on the + // socket + bool m_writing = false; + + std::vector m_send_buffer; +}; + +inline void add_fake_peer(lt::torrent_handle& h, int const i) +{ + char ep[30]; + std::snprintf(ep, sizeof(ep), "60.0.0.%d", i); + h.connect_peer(lt::tcp::endpoint( + asio::ip::make_address_v4(ep), 6881)); +} + +inline void add_fake_peers(lt::torrent_handle& h, int const n = 5) +{ + // add the fake peers + for (int i = 0; i < n; ++i) + { + add_fake_peer(h, i); + } +} + +struct udp_server +{ + udp_server(simulation& sim, char const* ip, int port + , std::function(char const*, int)> handler) + : m_ioc(sim, asio::ip::make_address(ip)) + , m_handler(handler) + { + boost::system::error_code ec; + m_socket.open(asio::ip::udp::v4(), ec); + TEST_CHECK(!ec); + m_socket.bind(asio::ip::udp::endpoint(asio::ip::address_v4::any() + , static_cast(port)), ec); + TEST_CHECK(!ec); + + m_socket.non_blocking(true); + + std::printf("udp_server::async_read_some\n"); + using namespace std::placeholders; + m_socket.async_receive_from(boost::asio::buffer(m_in_buffer) + , m_from, 0, std::bind(&udp_server::on_read, this, _1, _2)); + } + + void close() { m_socket.close(); } + +private: + + void on_read(boost::system::error_code const& ec, size_t bytes_transferred) + { + std::printf("udp_server::async_read_some callback. ec: %s transferred: %d\n" + , ec.message().c_str(), int(bytes_transferred)); + if (ec) return; + + std::vector send_buffer = m_handler(m_in_buffer.data(), int(bytes_transferred)); + + if (!send_buffer.empty()) + { + lt::error_code err; + m_socket.send_to(boost::asio::buffer(send_buffer), m_from, 0, err); + if (err) + { + std::printf("send_to FAILED: %s\n", err.message().c_str()); + } + else + { + std::printf("udp_server responding with %d bytes\n" + , int(send_buffer.size())); + } + } + + std::printf("udp_server::async_read_some\n"); + using namespace std::placeholders; + m_socket.async_receive_from(boost::asio::buffer(m_in_buffer) + , m_from, 0, std::bind(&udp_server::on_read, this, _1, _2)); + } + + std::array m_in_buffer; + + asio::io_context m_ioc; + asio::ip::udp::socket m_socket{m_ioc}; + asio::ip::udp::endpoint m_from; + + std::function(char const*, int)> m_handler; +}; + +struct fake_node : udp_server +{ + fake_node(simulation& sim, char const* ip, int port = 6881) + : udp_server(sim, ip, port, [&](char const* incoming, int size) + { + lt::bdecode_node n; + boost::system::error_code err; + int const ret = bdecode(incoming, incoming + size, n, err, nullptr, 10, 200); + TEST_EQUAL(ret, 0); + + m_incoming_packets.emplace_back(incoming, incoming + size); + + // TODO: ideally we would validate the DHT message + m_tripped = true; + return std::vector(); + }) + {} + + bool tripped() const { return m_tripped; } + + std::vector> const& incoming_packets() const + { return m_incoming_packets; } + +private: + + std::vector> m_incoming_packets; + bool m_tripped = false; +}; + +template +void check_accepted(std::array& test_peers + , std::array expected) +{ + int idx = 0; + for (auto p : test_peers) + { + TEST_EQUAL(p->accepted(), expected[idx]); + ++idx; + } +} + +template +void check_connected(std::array& test_peers + , std::array expected) +{ + int idx = 0; + for (auto p : test_peers) + { + TEST_EQUAL(p->connected(), expected[idx]); + ++idx; + } +} + +template +void check_disconnected(std::array& test_peers + , std::array expected) +{ + int idx = 0; + for (auto p : test_peers) + { + TEST_EQUAL(p->disconnected(), expected[idx]); + ++idx; + } +} + +#endif + diff --git a/simulation/make_proxy_settings.hpp b/simulation/make_proxy_settings.hpp new file mode 100644 index 0000000..f073dab --- /dev/null +++ b/simulation/make_proxy_settings.hpp @@ -0,0 +1,57 @@ +/* + +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_MAKE_PROXY_SETTINGS_HPP +#define TORRENT_MAKE_PROXY_SETTINGS_HPP + +#include "libtorrent/aux_/proxy_settings.hpp" + +inline lt::aux::proxy_settings make_proxy_settings( + lt::settings_pack::proxy_type_t const proxy_type) +{ + using namespace lt; + + aux::proxy_settings ps; + ps.type = proxy_type; + ps.proxy_hostnames = false; + // this IP and ports are specific to test_http_connection.cpp + if (proxy_type != settings_pack::none) + { + ps.hostname = "50.50.50.50"; + ps.port = proxy_type == settings_pack::http ? 4445 : 4444; + ps.username = "testuser"; + ps.password = "testpass"; + } + return ps; +} + +#endif diff --git a/simulation/setup_dht.cpp b/simulation/setup_dht.cpp new file mode 100644 index 0000000..199ccfe --- /dev/null +++ b/simulation/setup_dht.cpp @@ -0,0 +1,318 @@ +/* + +Copyright (c) 2014-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/kademlia/dht_settings.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/kademlia/node.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "setup_transfer.hpp" +#include // for unique_ptr +#include +#include "libtorrent/socket_io.hpp" // print_endpoint +#include "libtorrent/random.hpp" +#include "libtorrent/crc32c.hpp" +#include "libtorrent/alert_types.hpp" // for dht_routing_bucket +#include "libtorrent/aux_/listen_socket_handle.hpp" + +#include "setup_dht.hpp" + +using namespace sim; +using namespace lt; + +#ifndef TORRENT_DISABLE_DHT + +namespace { + + // this is the IP address assigned to node 'idx' + asio::ip::address addr_from_int(int /* idx */) + { + return rand_v4(); + } + + asio::ip::address addr6_from_int(int /* idx */) + { + asio::ip::address_v6::bytes_type bytes; + for (uint8_t& b : bytes) b = uint8_t(lt::random(0xff)); + return asio::ip::address_v6(bytes); + } + + // this is the node ID assigned to node 'idx' + dht::node_id id_from_addr(lt::address const& addr) + { + return dht::generate_id_impl(addr, 0); + } + + std::shared_ptr sim_listen_socket(tcp::endpoint ep) + { + auto ls = std::make_shared(); + ls->external_address.cast_vote(ep.address() + , lt::aux::session_interface::source_dht, lt::address()); + ls->local_endpoint = ep; + return ls; + } + +} // anonymous namespace + +struct dht_node final : lt::dht::socket_manager +{ + dht_node(sim::simulation& sim, lt::aux::session_settings const& sett, lt::counters& cnt + , int const idx, std::uint32_t const flags) + : m_io_context(sim, (flags & dht_network::bind_ipv6) ? addr6_from_int(idx) : addr_from_int(idx)) + , m_dht_storage(lt::dht::dht_default_storage_constructor(m_settings)) + , m_add_dead_nodes((flags & dht_network::add_dead_nodes) != 0) + , m_ipv6((flags & dht_network::bind_ipv6) != 0) + , m_socket(m_io_context) + , m_ls(sim_listen_socket(tcp::endpoint(m_io_context.get_ips().front(), 6881))) + , m_dht(m_ls, this, sett, id_from_addr(m_io_context.get_ips().front()) + , nullptr, cnt + , [](lt::dht::node_id const&, std::string const&) -> lt::dht::node* { return nullptr; } + , *m_dht_storage) + { + m_dht_storage->update_node_ids({id_from_addr(m_io_context.get_ips().front())}); + sock().open(m_ipv6 ? asio::ip::udp::v6() : asio::ip::udp::v4()); + sock().bind(asio::ip::udp::endpoint( + m_ipv6 ? lt::address(lt::address_v6::any()) : lt::address(lt::address_v4::any()), 6881)); + + sock().non_blocking(true); + sock().async_receive_from(asio::buffer(m_buffer.data(), m_buffer.size()) + , m_ep, [&](lt::error_code const& ec, std::size_t bytes_transferred) + { this->on_read(ec, bytes_transferred); }); + } + + // This type is not copyable, because the socket and the dht node is not + // copyable. + dht_node(dht_node const&) = delete; + dht_node& operator=(dht_node const&) = delete; + + // it's also not movable, because it passes in its this-pointer to the async + // receive function, which pins this object down. However, std::vector cannot + // hold non-movable and non-copyable types. + dht_node(dht_node&& n) = delete; + dht_node& operator=(dht_node&&) = delete; + + void on_read(lt::error_code const& ec, std::size_t bytes_transferred) + { + if (ec) return; + + using lt::entry; + using lt::bdecode; + + int pos; + error_code err; + + // since the simulation is single threaded, we can get away with just + // allocating a single of these + static bdecode_node msg; + int const ret = bdecode(m_buffer.data(), m_buffer.data() + bytes_transferred, msg, err, &pos, 10, 500); + if (ret != 0) return; + + if (msg.type() != bdecode_node::dict_t) return; + + lt::dht::msg m(msg, m_ep); + dht().incoming(m_ls, m); + + sock().async_receive_from(asio::buffer(m_buffer.data(), m_buffer.size()) + , m_ep, [&](lt::error_code const& ec, std::size_t bytes_transferred) + { this->on_read(ec, bytes_transferred); }); + } + + bool has_quota() override { return true; } + bool send_packet(lt::aux::listen_socket_handle const&, entry& e, udp::endpoint const& addr) override + { + // since the simulaton is single threaded, we can get away with allocating + // just a single send buffer + static std::vector send_buf; + + send_buf.clear(); + bencode(std::back_inserter(send_buf), e); + sock().send_to(boost::asio::const_buffer(send_buf.data(), send_buf.size()), addr); + return true; + } + + // the node_id and IP address of this node + std::pair node_info() const + { + return std::make_pair(dht().nid(), lt::udp::endpoint(m_io_context.get_ips().front(), 6881)); + } + + void bootstrap(std::vector> const& nodes) + { + // we don't want to tell every node about every other node. That's way too + // expensive. instead. pick a random subset of nodes proportionate to the + // bucket it would fall into + + dht::node_id const id = dht().nid(); + + // the number of slots left per bucket + std::array nodes_per_bucket; + nodes_per_bucket.fill(8); + + // when we use the larger routing table, the low buckets are larger + nodes_per_bucket[0] = 128; + nodes_per_bucket[1] = 64; + nodes_per_bucket[2] = 32; + nodes_per_bucket[3] = 16; + + // pick nodes in random order to provide good connectivity + std::vector order(nodes.size()); + for (size_t i = 0; i < order.size(); ++i) order[i] = i; + + while (!order.empty()) + { + auto const idx = lt::random(static_cast(order.size() - 1)); + assert(idx >= 0 && idx < order.size()); + auto const& n = nodes[order[idx]]; + if (idx < order.size() - 1) order[idx] = order.back(); + order.pop_back(); + + if (n.first == id) continue; + int const bucket = 159 - dht::distance_exp(id, n.first); + +/* std::printf("%s ^ %s = %s %d\n" + , to_hex(id.to_string()).c_str() + , to_hex(n.first.to_string()).c_str() + , to_hex(dht::distance(id, n.first).to_string()).c_str() + , bucket); +*/ + // there are no more slots in this bucket, just move on + if (nodes_per_bucket[bucket] == 0) continue; + --nodes_per_bucket[bucket]; + bool const added = dht().m_table.node_seen(n.first, n.second, lt::random(300) + 10); + TEST_CHECK(added); + if (m_add_dead_nodes) + { + // generate a random node ID that would fall in `bucket` + dht::node_id const mask = dht::generate_prefix_mask(bucket + 1); + udp::endpoint const ep = rand_udp_ep(m_ipv6 ? rand_v6 : rand_v4); + dht::node_id target = dht::generate_id_impl(ep.address(), 0) & ~mask; + target |= id & mask; + dht().m_table.node_seen(target, ep, lt::random(300) + 10); + } + } +/* + for (int i = 0; i < 40; ++i) + { + std::printf("%d ", nodes_per_bucket[i]); + } + std::printf("\n"); +*/ +//#error add invalid IPs as well, to simulate churn + } + + void stop() + { + sock().close(); + } + + lt::dht::node& dht() { return m_dht; } + lt::dht::node const& dht() const { return m_dht; } + +private: + lt::aux::session_settings m_settings; + asio::io_context m_io_context; + std::shared_ptr m_dht_storage; + bool const m_add_dead_nodes; + bool const m_ipv6; + lt::udp::socket m_socket; + lt::udp::socket& sock() { return m_socket; } + std::shared_ptr m_ls; + lt::dht::node m_dht; + lt::udp::endpoint m_ep; + std::array m_buffer; +}; + +dht_network::dht_network(sim::simulation& sim, int num_nodes, std::uint32_t flags) +{ + m_sett.set_bool(settings_pack::dht_ignore_dark_internet, false); + m_sett.set_bool(settings_pack::dht_restrict_routing_ips, false); + +// TODO: how can we introduce churn among peers? + + std::vector> all_nodes; + all_nodes.reserve(num_nodes); + + for (int i = 0; i < num_nodes; ++i) + { + // node 0 is the one we log + m_nodes.emplace_back(sim, m_sett, m_cnt, i, flags); + all_nodes.push_back(m_nodes.back().node_info()); + } + + for (auto& n : m_nodes) n.bootstrap(all_nodes); +} + +dht_network::~dht_network() = default; + +void print_routing_table(std::vector const& rt) +{ + int bucket = 0; + for (std::vector::const_iterator i = rt.begin() + , end(rt.end()); i != end; ++i, ++bucket) + { + char const* progress_bar = + "################################" + "################################" + "################################" + "################################"; + char const* short_progress_bar = "--------"; + std::printf("%3d [%3d, %d] %s%s\n" + , bucket, i->num_nodes, i->num_replacements + , progress_bar + (128 - i->num_nodes) + , short_progress_bar + (8 - std::min(8, i->num_replacements))); + } +} + +std::vector dht_network::router_nodes() const +{ + int idx = 0; + std::vector ret; + ret.reserve(8); + for (auto const& n : m_nodes) + { + if (idx >= 8) break; + ++idx; + ret.push_back(n.node_info().second); + } + return ret; +} + +void dht_network::stop() +{ + for (auto& n : m_nodes) n.stop(); +} + +#endif // TORRENT_DISABLE_DHT + diff --git a/simulation/setup_dht.hpp b/simulation/setup_dht.hpp new file mode 100644 index 0000000..6590eab --- /dev/null +++ b/simulation/setup_dht.hpp @@ -0,0 +1,74 @@ + +/* + +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_SETUP_DHT_HPP_INCLUDED +#define TORRENT_SETUP_DHT_HPP_INCLUDED + +#include +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/alert_types.hpp" // for dht_routing_bucket + +namespace sim +{ + struct simulation; +} + +struct dht_node; + +void print_routing_table(std::vector const& rt); + +struct dht_network +{ + enum flags_t + { + add_dead_nodes = 1, + bind_ipv6 = 2 + }; + + dht_network(sim::simulation& sim, int num_nodes, std::uint32_t flags = 0); + ~dht_network(); + + void stop(); + std::vector router_nodes() const; + +private: + + // used for all the nodes in the network + lt::counters m_cnt; + lt::aux::session_settings m_sett; + std::list m_nodes; +}; + +#endif + diff --git a/simulation/setup_swarm.cpp b/simulation/setup_swarm.cpp new file mode 100644 index 0000000..14f4d10 --- /dev/null +++ b/simulation/setup_swarm.cpp @@ -0,0 +1,428 @@ +/* + +Copyright (c) 2014-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/session.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/random.hpp" +#include "disk_io.hpp" +#include + +#include "settings.hpp" +#include "setup_swarm.hpp" +#include "setup_transfer.hpp" // for create_torrent, addr +#include "utils.hpp" +#include "simulator/queue.hpp" + +using namespace sim; + +#define DEBUG_SWARM 0 + +constexpr swarm_test_t swarm_test::download; +constexpr swarm_test_t swarm_test::upload; +constexpr swarm_test_t swarm_test::no_auto_stop; +constexpr swarm_test_t swarm_test::large_torrent; +constexpr swarm_test_t swarm_test::real_disk; + +namespace { + +int transfer_rate(lt::address ip) +{ + // in order to get a heterogeneous network, the last digit in the IP + // address determines the latency to that node as well as upload and + // download rates. + int last_digit; + if (ip.is_v4()) + last_digit = ip.to_v4().to_bytes()[3]; + else + last_digit = ip.to_v6().to_bytes()[15]; + return (last_digit + 4) * 5; +} + +} // anonymous namespace + +using duration = sim::chrono::high_resolution_clock::duration; +using sim::chrono::milliseconds; + +dsl_config::dsl_config(int kb_per_second, int send_queue_size) + : m_rate(kb_per_second) + , m_queue_size(send_queue_size) +{} + +sim::route dsl_config::incoming_route(asio::ip::address ip) +{ + int const rate = m_rate > 0 ? m_rate : transfer_rate(ip); + int const queue_size = m_queue_size > 0 ? m_queue_size : 200000; + + auto it = m_incoming.find(ip); + if (it != m_incoming.end()) return sim::route().append(it->second); + it = m_incoming.insert(it, std::make_pair(ip, std::make_shared( + m_sim->get_io_context() + , rate * 1000 + , lt::duration_cast(milliseconds(rate / 2)) + , queue_size, "DSL modem in"))); + return sim::route().append(it->second); +} + +sim::route dsl_config::outgoing_route(asio::ip::address ip) +{ + int const rate = m_rate > 0 ? m_rate : transfer_rate(ip); + int const queue_size = m_queue_size > 0 ? m_queue_size : 200000; + + auto it = m_outgoing.find(ip); + if (it != m_outgoing.end()) return sim::route().append(it->second); + it = m_outgoing.insert(it, std::make_pair(ip, std::make_shared( + m_sim->get_io_context(), rate * 1000 + , lt::duration_cast(milliseconds(rate / 2)) + , queue_size, "DSL modem out"))); + return sim::route().append(it->second); +} + +namespace { +bool should_print(lt::alert* a) +{ + using namespace lt; + +#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) + return false; + } +#endif + if (alert_cast(a) + || alert_cast(a) + || alert_cast(a) + || alert_cast(a)) + { + return false; + } + return true; +} +} + +void setup_swarm(int num_nodes + , swarm_test_t type + , std::function new_session + , std::function add_torrent + , std::function on_alert + , std::function terminate) +{ + dsl_config network_cfg; + sim::simulation sim{network_cfg}; + + setup_swarm(num_nodes, type, sim, new_session + , add_torrent, on_alert, terminate); +} + +void setup_swarm(int num_nodes + , swarm_test_t type + , sim::simulation& sim + , std::function new_session + , std::function add_torrent + , std::function on_alert + , std::function terminate) +{ + lt::settings_pack pack = settings(); + + lt::add_torrent_params p; + p.flags &= ~lt::torrent_flags::paused; + p.flags &= ~lt::torrent_flags::auto_managed; + + setup_swarm(num_nodes, type, sim, pack, p, new_session + , add_torrent, on_alert, terminate); +} + +void setup_swarm(int num_nodes + , swarm_test_t type + , sim::simulation& sim + , lt::settings_pack const& default_settings + , lt::add_torrent_params const& default_add_torrent + , std::function new_session + , std::function add_torrent + , std::function on_alert + , std::function terminate) +{ + setup_swarm(num_nodes, type, sim + , default_settings + , default_add_torrent + , [](lt::session&) {} + , new_session + , add_torrent + , on_alert + , terminate); +} + +void setup_swarm(int num_nodes + , swarm_test_t const type + , sim::simulation& sim + , lt::settings_pack const& default_settings + , lt::add_torrent_params const& default_add_torrent + , std::function init_session + , std::function new_session + , std::function add_torrent + , std::function on_alert + , std::function terminate) +{ + asio::io_context ios(sim); + lt::time_point start_time(lt::clock_type::now()); + + std::vector> nodes; + std::vector> io_context; + std::vector zombies; + lt::deadline_timer timer(ios); + + lt::error_code ec; + int const swarm_id = unit_test::test_counter(); + std::string path = save_path(swarm_id, 0); + + std::shared_ptr ti; + + if (type & swarm_test::real_disk) + { + lt::create_directory(path, ec); + if (ec) std::printf("failed to create directory: \"%s\": %s\n" + , path.c_str(), ec.message().c_str()); + std::ofstream file(lt::combine_path(path, "temporary").c_str()); + ti = ::create_torrent(&file, "temporary", 0x4000, (type & swarm_test::large_torrent) ? 50 : 9, false); + } + else + { + ti = ::create_test_torrent(0x4000, (type & swarm_test::large_torrent) ? 50 : 9, {}); + } + + if (bool(type & swarm_test::download) && bool(type & swarm_test::upload)) + { + TEST_ERROR("can only use one of upload or download test type"); + } + + // session 0 is the one we're testing. The others provide the scaffolding + // it's either a downloader or a seed + for (int i = 0; i < num_nodes; ++i) + { + // create a new io_context + std::vector ips; + char ep[30]; + std::snprintf(ep, sizeof(ep), "50.0.%d.%d", (i + 1) >> 8, (i + 1) & 0xff); + ips.push_back(addr(ep)); + std::snprintf(ep, sizeof(ep), "2000::%X%X", (i + 1) >> 8, (i + 1) & 0xff); + ips.push_back(addr(ep)); + io_context.push_back(std::make_shared(sim, ips)); + + lt::session_params params; + params.settings = default_settings; + + // make sure the sessions have different peer ids + lt::peer_id pid; + lt::aux::random_bytes(pid); + params.settings.set_str(lt::settings_pack::peer_fingerprint, pid.to_string()); + + if (!(type & swarm_test::real_disk)) + { + if (type & swarm_test::download) + { + // in download tests, session 0 is a downloader and every other session + // is a seed. save path 0 is where the files are, so that's for seeds + params.disk_io_constructor = test_disk().set_seed(i > 0); + } + else + { + // in seed tests, session 0 is a seed and every other session + // a downloader. save path 0 is where the files are, so that's for seeds + params.disk_io_constructor = test_disk().set_seed(i == 0); + } + } + + if (i == 0) new_session(params.settings); + + std::shared_ptr ses = + std::make_shared(params, *io_context.back()); + init_session(*ses); + nodes.push_back(ses); + + if (i > 0) + { + // the other sessions should not talk to each other + lt::ip_filter filter; + filter.add_rule(addr("0.0.0.0"), addr("255.255.255.255"), lt::ip_filter::blocked); + filter.add_rule(addr("50.0.0.1"), addr("50.0.0.1"), 0); + ses->set_ip_filter(filter); + } + + lt::add_torrent_params p = default_add_torrent; + if (type & swarm_test::real_disk) + { + if (type & swarm_test::download) + { + // in download tests, session 0 is a downloader and every other session + // is a seed. save path 0 is where the files are, so that's for seeds + p.save_path = save_path(swarm_id, i > 0 ? 0 : 1); + } + else + { + // in seed tests, session 0 is a seed and every other session + // a downloader. save path 0 is where the files are, so that's for seeds + p.save_path = save_path(swarm_id, i); + } + } + else + { + p.save_path = "."; + } + + p.ti = ti; + if (i == 0) add_torrent(p); + ses->async_add_torrent(p); + + ses->set_alert_notify([&, i]() { + // this function is called inside libtorrent and we cannot perform work + // immediately in it. We have to notify the outside to pull all the alerts + post(*io_context[i], [&,i]() + { + lt::session* ses = nodes[i].get(); + + // when shutting down, we may have destructed the session + if (ses == nullptr) return; + + std::vector alerts; + ses->pop_alerts(&alerts); + + // to debug the sessions not under test, comment out the following + // line +#if DEBUG_SWARM == 0 + if (i != 0) return; +#endif + + for (lt::alert* a : alerts) + { + // only print alerts from the session under test + lt::time_duration d = a->timestamp() - start_time; + std::uint32_t const millis = std::uint32_t( + lt::duration_cast(d).count()); + + if (should_print(a)) + { + std::printf( +#if DEBUG_SWARM != 0 + "[%d] " +#endif + "%4d.%03d: %-25s %s\n" +#if DEBUG_SWARM != 0 + , i +#endif + , millis / 1000, millis % 1000 + , a->what() + , a->message().c_str()); + } + +#if DEBUG_SWARM != 0 + if (i != 0) continue; +#endif + + // if a torrent was added save the torrent handle + if (lt::add_torrent_alert* at = lt::alert_cast(a)) + { + lt::torrent_handle h = at->handle; + + // now, connect this torrent to all the others in the swarm + // start at 1 to avoid self-connects + for (int k = 1; k < num_nodes; ++k) + { + // TODO: the pattern of creating an address from a format + // string and an integer is common. It should probably be + // factored out into its own function + char ep[30]; + std::snprintf(ep, sizeof(ep), "50.0.%d.%d", (k + 1) >> 8, (k + 1) & 0xff); + h.connect_peer(lt::tcp::endpoint(addr(ep), 6881)); + } + } + + on_alert(a, *ses); + } + }); + }); + } + + int tick = 0; + std::function on_tick + = [&](lt::error_code const& ec) + { + if (ec) return; + + bool shut_down = terminate(tick, *nodes[0]); + + if ((type & swarm_test::upload) && !bool(type & swarm_test::no_auto_stop)) + { + shut_down |= std::all_of(nodes.begin() + 1, nodes.end() + , [](std::shared_ptr const& s) + { return is_seed(*s); }) && num_nodes > 1; + + if (tick > 88 * (num_nodes - 1) && !shut_down && num_nodes > 1) + { + TEST_ERROR("seeding failed!"); + shut_down = true; + } + } + + if (shut_down) + { + std::printf("TERMINATING\n"); + + // terminate simulation + for (int i = 0; i < int(nodes.size()); ++i) + { + zombies.push_back(nodes[i]->abort()); + nodes[i].reset(); + } + return; + } + + ++tick; + + timer.expires_after(lt::seconds(1)); + timer.async_wait(on_tick); + }; + + timer.expires_after(lt::seconds(1)); + timer.async_wait(on_tick); + + sim.run(); +} + diff --git a/simulation/setup_swarm.hpp b/simulation/setup_swarm.hpp new file mode 100644 index 0000000..c03c1bb --- /dev/null +++ b/simulation/setup_swarm.hpp @@ -0,0 +1,101 @@ +/* + +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 "simulator/simulator.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/flags.hpp" +#include + +#ifndef TORRENT_SETUP_SWARM_HPP_INCLUDED +#define TORRENT_SETUP_SWARM_HPP_INCLUDED + +using lt::operator""_bit; +using swarm_test_t = lt::flags::bitfield_flag; + +struct swarm_test +{ + constexpr static swarm_test_t download = 0_bit; + constexpr static swarm_test_t upload = 1_bit; + constexpr static swarm_test_t no_auto_stop = 2_bit; + constexpr static swarm_test_t large_torrent = 3_bit; + constexpr static swarm_test_t real_disk = 4_bit; +}; + +void setup_swarm(int num_nodes + , swarm_test_t type + , std::function new_session + , std::function add_torrent + , std::function on_alert + , std::function terminate); + +void setup_swarm(int num_nodes + , swarm_test_t type + , sim::simulation& sim + , std::function new_session + , std::function add_torrent + , std::function on_alert + , std::function terminate); + +void setup_swarm(int num_nodes + , swarm_test_t type + , sim::simulation& sim + , lt::settings_pack const& default_settings + , lt::add_torrent_params const& default_add_torrent + , std::function new_session + , std::function add_torrent + , std::function on_alert + , std::function terminate); + +void setup_swarm(int num_nodes + , swarm_test_t type + , sim::simulation& sim + , lt::settings_pack const& default_settings + , lt::add_torrent_params const& default_add_torrent + , std::function init_session + , std::function new_session + , std::function add_torrent + , std::function on_alert + , std::function terminate); + +struct dsl_config : sim::default_config +{ + dsl_config(int kb_per_second = 0, int send_queue_size = 0); + virtual sim::route incoming_route(lt::address ip) override; + virtual sim::route outgoing_route(lt::address ip) override; +private: + int m_rate; // kilobytes per second + int m_queue_size; // bytes +}; + +#endif + diff --git a/simulation/test_auto_manage.cpp b/simulation/test_auto_manage.cpp new file mode 100644 index 0000000..3064d00 --- /dev/null +++ b/simulation/test_auto_manage.cpp @@ -0,0 +1,917 @@ +/* + +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/session.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "settings.hpp" +#include "utils.hpp" +#include "create_torrent.hpp" +#include "simulator/simulator.hpp" +#include "simulator/utils.hpp" // for timer +#include + +using namespace sim; +using namespace lt; + +const int num_torrents = 10; + +using sim::asio::ip::address_v4; + +// this is the general template for these tests. create the session with custom +// settings (Settings), set up the test, by adding torrents with certain +// arguments (Setup), run the test and verify the end state (Test) +template +void run_test(Settings const& sett, Setup const& setup, Test const& test) +{ + // setup the simulation + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + std::unique_ptr ios = make_io_context(sim, 0); + lt::session_proxy zombie; + + // setup settings pack to use for the session (customization point) + lt::settings_pack pack = settings(); + sett(pack); + + // create session + std::shared_ptr ses = std::make_shared(pack, *ios); + + // set up test, like adding torrents (customization point) + setup(*ses); + + // set up a timer to fire later, to verify everything we expected to happen + // happened + sim::timer t(sim, lt::seconds((num_torrents + 1) * 60) + , [&](boost::system::error_code const&) + { + test(*ses); + + // shut down + zombie = ses->abort(); + ses.reset(); + }); + + sim.run(); +} + +TORRENT_TEST(dont_count_slow_torrents) +{ + run_test( + [](lt::settings_pack& sett) { + // session settings + sett.set_bool(lt::settings_pack::dont_count_slow_torrents, true); + sett.set_int(lt::settings_pack::active_downloads, 1); + sett.set_int(lt::settings_pack::active_seeds, 1); + }, + + [](lt::session& ses) { + // add torrents + for (int i = 0; i < num_torrents; ++i) + { + lt::add_torrent_params params = ::create_torrent(i, false); + params.flags |= lt::torrent_flags::auto_managed; + params.flags |= lt::torrent_flags::paused; + ses.async_add_torrent(params); + } + }, + + [](lt::session& ses) { + // verify result + + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point last = lt::time_point::min(); + lt::time_point start_time = alerts[0]->timestamp(); + + int num_started = 0; + for (alert* a : alerts) + { + std::printf("%-3d %s\n", int(duration_cast(a->timestamp() + - start_time).count()), a->message().c_str()); + if (alert_cast(a) == nullptr) continue; + + lt::time_point t = a->timestamp(); + if (last != lt::time_point::min()) + { + // expect starting of new torrents to be spaced by 60 seconds + // the division by 2 is to allow some slack (it's integer + // division) + TEST_EQUAL(duration_cast(t - last).count() / 2, 60 / 2); + } + last = t; + ++num_started; + } + + TEST_EQUAL(num_started, num_torrents); + + for (torrent_handle const& h : ses.get_torrents()) + { + TEST_CHECK(h.status().flags & torrent_flags::auto_managed); + TEST_CHECK(!(h.status().flags & torrent_flags::paused)); + } + }); +} + +TORRENT_TEST(count_slow_torrents) +{ + run_test( + [](settings_pack& sett) { + // session settings + sett.set_bool(settings_pack::dont_count_slow_torrents, false); + sett.set_int(settings_pack::active_downloads, 1); + sett.set_int(settings_pack::active_seeds, 1); + }, + + [](lt::session& ses) { + // add torrents + for (int i = 0; i < num_torrents; ++i) + { + lt::add_torrent_params params = ::create_torrent(i, false); + params.flags |= torrent_flags::auto_managed; + params.flags |= torrent_flags::paused; + ses.async_add_torrent(params); + } + }, + + [](lt::session& ses) { + // verify result (only one should have been started, even though + // they're all idle) + + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + int num_started = 0; + for (alert* a : alerts) + { + std::printf("%-3d %s\n", int(duration_cast(a->timestamp() + - start_time).count()), a->message().c_str()); + if (alert_cast(a) == nullptr) continue; + ++num_started; + } + + TEST_EQUAL(num_started, 1); + + num_started = 0; + for (torrent_handle const& h : ses.get_torrents()) + { + TEST_CHECK(h.status().flags & torrent_flags::auto_managed); + num_started += !(h.status().flags & torrent_flags::paused); + } + TEST_EQUAL(num_started, 1); + }); +} + +TORRENT_TEST(force_stopped_download) +{ + run_test( + [](settings_pack& sett) { + // session settings + sett.set_bool(settings_pack::dont_count_slow_torrents, true); + sett.set_int(settings_pack::active_downloads, 10); + sett.set_int(settings_pack::active_seeds, 10); + }, + + [](lt::session& ses) { + // add torrents + for (int i = 0; i < num_torrents; ++i) + { + lt::add_torrent_params params = ::create_torrent(i, false); + // torrents are paused and not auto-managed + params.flags &= ~torrent_flags::auto_managed; + params.flags |= torrent_flags::paused; + ses.async_add_torrent(params); + } + }, + + [](lt::session& ses) { + // verify result (none should have been started) + + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + for (alert* a : alerts) + { + std::printf("%-3d %s\n", int(duration_cast(a->timestamp() + - start_time).count()), a->message().c_str()); + // we don't expect any torrents being started or stopped, since + // they're all force stopped + TEST_CHECK(alert_cast(a) == nullptr); + TEST_CHECK(alert_cast(a) == nullptr); + } + + for (torrent_handle const& h : ses.get_torrents()) + { + TEST_CHECK(!(h.status().flags & torrent_flags::auto_managed)); + TEST_CHECK(h.status().flags & torrent_flags::paused); + } + }); +} + +TORRENT_TEST(force_started) +{ + run_test( + [](settings_pack& sett) { + // session settings + sett.set_bool(settings_pack::dont_count_slow_torrents, false); + sett.set_int(settings_pack::active_downloads, 1); + sett.set_int(settings_pack::active_seeds, 1); + }, + + [](lt::session& ses) { + // add torrents + for (int i = 0; i < num_torrents; ++i) + { + lt::add_torrent_params params = ::create_torrent(i, false); + // torrents are started and not auto-managed + params.flags &= ~torrent_flags::auto_managed; + params.flags &= ~torrent_flags::paused; + ses.async_add_torrent(params); + } + }, + + [](lt::session& ses) { + // verify result (none should have been started) + + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + for (alert* a : alerts) + { + std::printf("%-3d %s\n", int(duration_cast(a->timestamp() + - start_time).count()), a->message().c_str()); + // we don't expect any torrents being started or stopped, since + // they're all force started + TEST_CHECK(alert_cast(a) == nullptr); + TEST_CHECK(alert_cast(a) == nullptr); + } + + for (torrent_handle const& h : ses.get_torrents()) + { + TEST_CHECK(!(h.status().flags & torrent_flags::auto_managed)); + TEST_CHECK(!(h.status().flags & torrent_flags::paused)); + } + }); +} + +TORRENT_TEST(seed_limit) +{ + run_test( + [](settings_pack& sett) { + // session settings + // set the seed limit to 3 + sett.set_bool(settings_pack::dont_count_slow_torrents, false); + sett.set_int(settings_pack::active_checking, 1); + sett.set_int(settings_pack::active_seeds, 3); + }, + + [](lt::session& ses) { + // add torrents + // add 5 seeds + for (int i = 0; i < num_torrents; ++i) + { + lt::add_torrent_params params = ::create_torrent(i, true); + // torrents are paused and auto-managed + params.flags |= torrent_flags::auto_managed; + params.flags |= torrent_flags::paused; + ses.async_add_torrent(params); + } + }, + + [](lt::session& ses) { + // make sure only 3 got started + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + int num_started = 0; + int num_checking = 0; + int num_seeding = 0; + for (alert* a : alerts) + { + std::printf("%-3d %s\n", int(duration_cast(a->timestamp() + - start_time).count()), a->message().c_str()); + if (alert_cast(a)) + { + ++num_started; + + std::printf("started: %d checking: %d seeding: %d\n" + , num_started, num_checking, num_seeding); + } + else if (alert_cast(a)) + { + TEST_CHECK(num_started > 0); + --num_started; + + std::printf("started: %d checking: %d seeding: %d\n" + , num_started, num_checking, num_seeding); + } + else if (state_changed_alert* sc = alert_cast(a)) + { + if (sc->prev_state == torrent_status::checking_files) + --num_checking; + else if (sc->prev_state == torrent_status::seeding) + --num_seeding; + + if (sc->state == torrent_status::checking_files) + ++num_checking; + else if (sc->state == torrent_status::seeding) + ++num_seeding; + + std::printf("started: %d checking: %d seeding: %d\n" + , num_started, num_checking, num_seeding); + + // while at least one torrent is checking, there may be another + // started torrent (the checking one), other than that, only 3 + // torrents are allowed to be started and seeding + TEST_CHECK(num_started <= 3 + 1); + TEST_CHECK(num_started <= 1 || num_seeding > 0); + } + } + + TEST_EQUAL(num_started, 3); + + num_started = 0; + for (torrent_handle const& h : ses.get_torrents()) + { + TEST_CHECK(h.status().flags & torrent_flags::auto_managed); + TEST_CHECK(h.status().is_seeding); + num_started += !(h.status().flags & torrent_flags::paused); + } + TEST_EQUAL(num_started, 3); + }); +} + +TORRENT_TEST(download_limit) +{ + run_test( + [](settings_pack& sett) { + // session settings + // set the seed limit to 3 + sett.set_bool(settings_pack::dont_count_slow_torrents, false); + sett.set_int(settings_pack::active_checking, 1); + sett.set_int(settings_pack::active_downloads, 3); + }, + + [](lt::session& ses) { + // add torrents + // add 5 seeds + for (int i = 0; i < num_torrents; ++i) + { + lt::add_torrent_params params = ::create_torrent(i, false); + // torrents are paused and auto-managed + params.flags |= torrent_flags::auto_managed; + params.flags |= torrent_flags::paused; + ses.async_add_torrent(params); + } + }, + + [](lt::session& ses) { + // make sure only 3 got started + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + int num_started = 0; + int num_checking = 0; + int num_downloading = 0; + for (alert* a : alerts) + { + std::printf("%-3d %s\n", int(duration_cast(a->timestamp() + - start_time).count()), a->message().c_str()); + if (alert_cast(a)) + { + ++num_started; + + std::printf("started: %d checking: %d downloading: %d\n" + , num_started, num_checking, num_downloading); + } + else if (alert_cast(a)) + { + TEST_CHECK(num_started > 0); + --num_started; + + std::printf("started: %d checking: %d downloading: %d\n" + , num_started, num_checking, num_downloading); + } + else if (state_changed_alert* sc = alert_cast(a)) + { + if (sc->prev_state == torrent_status::checking_files) + --num_checking; + else if (sc->prev_state == torrent_status::downloading) + --num_downloading; + + if (sc->state == torrent_status::checking_files) + ++num_checking; + else if (sc->state == torrent_status::downloading) + ++num_downloading; + + std::printf("started: %d checking: %d downloading: %d\n" + , num_started, num_checking, num_downloading); + + // while at least one torrent is checking, there may be another + // started torrent (the checking one), other than that, only 3 + // torrents are allowed to be started and seeding + TEST_CHECK(num_started <= 3 + 1); + TEST_CHECK(num_started <= 1 || num_downloading > 0); + } + } + + TEST_EQUAL(num_started, 3); + + num_started = 0; + for (torrent_handle const& h : ses.get_torrents()) + { + TEST_CHECK(h.status().flags & torrent_flags::auto_managed); + TEST_CHECK(!h.status().is_finished); + num_started += !(h.status().flags & torrent_flags::paused); + } + TEST_EQUAL(num_started, 3); + }); +} +// make sure torrents don't announce to the tracker when transitioning from +// checking to paused downloading +TORRENT_TEST(checking_announce) +{ + run_test( + [](settings_pack& sett) { + // session settings + // set the seed limit to 3 + sett.set_bool(settings_pack::dont_count_slow_torrents, false); + sett.set_int(settings_pack::active_checking, 1); + + // just set the tracker retry intervals really long, to make sure we + // don't keep retrying the tracker (since there's nothing running + // there, it will fail) + sett.set_int(settings_pack::tracker_backoff, 100000); + // only the first torrent added should ever announce + sett.set_int(settings_pack::active_seeds, 1); + }, + + [](lt::session& ses) { + // add torrents + // add 5 seeds + for (int i = 0; i < num_torrents; ++i) + { + lt::add_torrent_params params = ::create_torrent(i, true); + // torrents are paused and auto-managed + params.flags |= torrent_flags::auto_managed; + params.flags |= torrent_flags::paused; + // we need this to get the tracker_announce_alert + params.trackers.push_back("http://10.10.0.2/announce"); + ses.async_add_torrent(params); + } + }, + + [](lt::session& ses) { + // make sure only 3 got started + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + int num_announce = 0; + for (alert* a : alerts) + { + std::printf("%-3d %s\n", int(duration_cast(a->timestamp() + - start_time).count()), a->message().c_str()); + if (alert_cast(a)) + ++num_announce; + } + + TEST_EQUAL(num_announce, 2); + + int num_started = 0; + for (torrent_handle const& h : ses.get_torrents()) + { + TEST_CHECK(h.status().flags & torrent_flags::auto_managed); + num_started += !(h.status().flags & torrent_flags::paused); + } + TEST_EQUAL(num_started, 1); + }); +} + +TORRENT_TEST(paused_checking) +{ + run_test( + [](settings_pack& sett) { + // session settings + // set the seed limit to 3 + sett.set_bool(settings_pack::dont_count_slow_torrents, true); + sett.set_int(settings_pack::active_checking, 1); + }, + + [](lt::session& ses) { + // add torrents + // add 5 seeds + for (int i = 0; i < num_torrents; ++i) + { + lt::add_torrent_params params = ::create_torrent(i, true); + // torrents are paused and auto-managed + params.flags &= ~torrent_flags::auto_managed; + params.flags |= torrent_flags::paused; + ses.async_add_torrent(params); + } + }, + + [](lt::session& ses) { + // make sure only 3 got started + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + for (alert* a : alerts) + { + std::printf("%-3d %s\n", int(duration_cast(a->timestamp() + - start_time).count()), a->message().c_str()); + if (state_changed_alert* sc = alert_cast(a)) + { + TEST_CHECK(sc->state == torrent_status::checking_files + || sc->state == torrent_status::checking_resume_data); + } + } + + for (torrent_handle const& h : ses.get_torrents()) + { + // even though all torrents are seeding, libtorrent shouldn't know + // that, because they should never have been checked (because they + // were force stopped) + TEST_CHECK(!h.status().is_seeding); + TEST_CHECK(!(h.status().flags & torrent_flags::auto_managed)); + TEST_CHECK(h.status().flags & torrent_flags::paused); + } + }); +} + +// set the stop_when_ready flag and make sure we receive a paused alert *before* +// a state_changed_alert +TORRENT_TEST(stop_when_ready) +{ + run_test( + [](settings_pack&) {}, + + [](lt::session& ses) { + // add torrents + lt::add_torrent_params params = ::create_torrent(0, true); + // torrents are started and auto-managed + params.flags |= torrent_flags::auto_managed; + params.flags |= torrent_flags::stop_when_ready; + // we need this to get the tracker_announce_alert + params.trackers.push_back("http://10.10.0.2/announce"); + ses.async_add_torrent(params); + }, + + [](lt::session& ses) { + // verify result (none should have been started) + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + int num_paused = 0; + for (alert* a : alerts) + { + std::printf("%-3d %s\n", int(duration_cast(a->timestamp() + - start_time).count()), a->message().c_str()); + + if (alert_cast(a)) + { + ++num_paused; + } + + if (state_changed_alert* sc = alert_cast(a)) + { + if (sc->state == torrent_status::seeding) + { + // once we turn into being a seed. we should have been paused + // already. + TEST_EQUAL(num_paused, 1); + } + } + // there should not have been any announces. the torrent should have + // been stopped *before* announcing. + TEST_CHECK(alert_cast(a) == nullptr); + } + + for (torrent_handle const& h : ses.get_torrents()) + { + // the torrent should have been force-stopped (after checking was + // donw, because we set the stop_when_ready flag). Force stopped + // means not auto-managed and paused. + torrent_status st = h.status(); + TEST_CHECK(!(st.flags & torrent_flags::auto_managed)); + TEST_CHECK(st.flags & torrent_flags::paused); + // it should be seeding. If it's not seeding it may not have had its + // files checked. + TEST_EQUAL(st.state, torrent_status::seeding); + } + }); +} + +// This test makes sure that the fastresume check will still run for stopped +// torrents. The actual checking of files won't start until the torrent is +// un-paused/resumed though +TORRENT_TEST(resume_reject_when_paused) +{ + run_test( + [](settings_pack& sett) { + sett.set_int(settings_pack::alert_mask, alert_category::all); + }, + + [](lt::session& ses) { + // add torrents + lt::add_torrent_params params = ::create_torrent(0, true); + + // the torrent is not auto managed and paused. Once the resume data + // check completes, it will stay paused but the state_changed_alert + // will be posted, when it goes to check the files + params.flags &= ~torrent_flags::auto_managed; + params.flags |= torrent_flags::paused; + + ses.async_add_torrent(params); + }, + + [](lt::session& ses) { + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + int num_piece_finished = 0; + int checking_files = 0; + int state_changed = 0; + + for (alert* a : alerts) + { + std::printf("%-3d %-25s %s\n", int(duration_cast(a->timestamp() + - start_time).count()) + , a->what() + , a->message().c_str()); + + if (alert_cast(a)) + ++num_piece_finished; + + if (auto sc = alert_cast(a)) + { + if (sc->state == torrent_status::checking_files) + ++checking_files; + ++state_changed; + } + } + + for (torrent_handle const& h : ses.get_torrents()) + { + // the torrent should have been force-stopped. Force stopped means + // not auto-managed and paused. + torrent_status st = h.status(); + TEST_CHECK(!(st.flags & torrent_flags::auto_managed)); + TEST_CHECK(st.flags & torrent_flags::paused); + // it should be checking files, because the resume data should have + // failed validation. + TEST_EQUAL(st.state, torrent_status::checking_files); + } + + TEST_EQUAL(num_piece_finished, 0); + // it should not actually check the files (since it's paused) + // if the files were checked, the state would change to downloading + // immediately, and state_changed would be 2. This asserts that's not + // the case. + TEST_EQUAL(state_changed, 1); + TEST_EQUAL(checking_files, 1); + }); +} + +// this test adds the torrent in paused state and no resume data. Expecting the +// resume check to complete and just transition into checking state, but without +// actually checking anything +TORRENT_TEST(no_resume_when_paused) +{ + run_test( + [](settings_pack& sett) { + sett.set_int(settings_pack::alert_mask, alert_category::all); + }, + + [](lt::session& ses) { + // add torrents + lt::add_torrent_params params = ::create_torrent(0, true); + + // the torrent is not auto managed and paused. + params.flags &= ~torrent_flags::auto_managed; + params.flags |= torrent_flags::paused; + + ses.async_add_torrent(params); + }, + + [](lt::session& ses) { + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + int num_piece_finished = 0; + int resume_rejected = 0; + int state_changed = 0; + + for (alert* a : alerts) + { + std::printf("%-3d %-25s %s\n", int(duration_cast(a->timestamp() + - start_time).count()) + , a->what() + , a->message().c_str()); + + if (alert_cast(a)) + ++num_piece_finished; + + if (alert_cast(a)) + ++resume_rejected; + + if (auto sc = alert_cast(a)) + { + if (sc->state == torrent_status::checking_files) + ++state_changed; + } + } + + for (torrent_handle const& h : ses.get_torrents()) + { + // the torrent should have been force-stopped. Force stopped means + // not auto-managed and paused. + torrent_status st = h.status(); + TEST_CHECK(!(st.flags & torrent_flags::auto_managed)); + TEST_CHECK(st.flags & torrent_flags::paused); + // it should be checking files, because the resume data should have + // failed validation. + TEST_EQUAL(st.state, torrent_status::checking_files); + } + + TEST_EQUAL(num_piece_finished, 0); + TEST_EQUAL(resume_rejected, 0); + TEST_EQUAL(state_changed, 1); + }); +} + +// this is just asserting that when the files are checked we do in fact get +// piece_finished_alerts. The other tests rely on this assumption +TORRENT_TEST(no_resume_when_started) +{ + run_test( + [](settings_pack& sett) { + sett.set_int(settings_pack::alert_mask, alert_category::all); + }, + + [](lt::session& ses) { + // add torrents + lt::add_torrent_params params = ::create_torrent(0, true); + ses.async_add_torrent(params); + }, + + [](lt::session& ses) { + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + int num_piece_finished = 0; + int state_changed = 0; + + for (alert* a : alerts) + { + std::printf("%-3d %-25s %s\n", int(duration_cast(a->timestamp() + - start_time).count()) + , a->what() + , a->message().c_str()); + + if (alert_cast(a)) + ++num_piece_finished; + + if (auto sc = alert_cast(a)) + { + if (sc->state == torrent_status::checking_files) + ++state_changed; + } + } + + TEST_EQUAL(num_piece_finished, 9); + TEST_EQUAL(state_changed, 1); + }); +} + +// when setting active_seeds to 0, any completed torrent should be paused +TORRENT_TEST(pause_completed_torrents) +{ + run_test( + [](settings_pack& sett) { + // session settings + sett.set_bool(settings_pack::dont_count_slow_torrents, true); + sett.set_int(settings_pack::active_downloads, 1); + sett.set_int(settings_pack::active_seeds, 0); + }, + + [](lt::session& ses) { + // add torrent + lt::add_torrent_params params = ::create_torrent(0, true); + params.flags |= torrent_flags::auto_managed; + params.flags |= torrent_flags::paused; + ses.async_add_torrent(params); + }, + + [](lt::session& ses) { + // verify result + // the torrent should have been paused immediately as it completed, + // since we don't allow any seeding torrents + + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + int num_started = 0; + int num_finished = 0; + int num_paused = 0; + lt::time_point finished; + lt::time_point paused; + for (alert* a : alerts) + { + std::printf("%-3d %s\n", int(duration_cast(a->timestamp() + - start_time).count()), a->message().c_str()); + if (alert_cast(a)) + ++num_started; + if (alert_cast(a)) + { + ++num_finished; + finished = a->timestamp(); + } + if (alert_cast(a)) + { + ++num_paused; + paused = a->timestamp(); + } + } + + TEST_EQUAL(num_started, 1); + TEST_EQUAL(num_finished, 1); + TEST_EQUAL(num_paused, 1); + + if (num_finished > 0 && num_paused > 0) + { + TEST_CHECK(paused >= finished); + TEST_CHECK(paused - finished < chrono::milliseconds(1)); + } + + num_paused = 0; + for (torrent_handle const& h : ses.get_torrents()) + { + TEST_CHECK(h.status().flags & torrent_flags::auto_managed); + num_paused += bool(h.status().flags & torrent_flags::paused); + } + TEST_EQUAL(num_paused, 1); + }); +} + + +// TODO: assert that the torrent_paused_alert is posted when pausing +// downloading, seeding, checking torrents as well as the graceful pause +// TODO: test limits of tracker, DHT and LSD announces + + diff --git a/simulation/test_checking.cpp b/simulation/test_checking.cpp new file mode 100644 index 0000000..93e99a3 --- /dev/null +++ b/simulation/test_checking.cpp @@ -0,0 +1,225 @@ +/* + +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/session.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/hex.hpp" +#include "simulator/simulator.hpp" +#include "simulator/utils.hpp" + +#include "test.hpp" +#include "settings.hpp" +#include "setup_transfer.hpp" // for create_random_files +#include "create_torrent.hpp" +#include "utils.hpp" + +#include +#include + +template +void run_test(Setup const& setup, Test const& test) +{ + // this is a seeding torrent + lt::add_torrent_params atp = create_torrent(0, true); + + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + auto ios = std::make_unique( + sim, lt::make_address_v4("50.0.0.1")); + lt::session_proxy zombie; + + // setup settings pack to use for the session (customization point) + lt::settings_pack pack = settings(); + setup(atp, pack); + + auto ses = std::make_shared(pack, *ios); + + ses->async_add_torrent(atp); + + print_alerts(*ses); + + sim::timer t(sim, lt::seconds(6), [&](boost::system::error_code const&) + { + test(*ses); + + // shut down + zombie = ses->abort(); + ses.reset(); + }); + + sim.run(); +} + +TORRENT_TEST(no_truncate_checking) +{ + std::string filename; + int size = 0; + run_test( + [&](lt::add_torrent_params& atp, lt::settings_pack&) { + filename = lt::combine_path(atp.save_path, atp.ti->files().file_path(lt::file_index_t{0})); + std::ofstream f(filename); + // create a file that's 100 bytes larger + size = int(atp.ti->files().file_size(lt::file_index_t{0}) + 100); + std::vector dummy(size); + f.write(dummy.data(), dummy.size()); + }, + [](lt::session&) {} + ); + + // file should not have been truncated just by checking + std::ifstream f(filename); + f.seekg(0, std::ios_base::end); + TEST_EQUAL(f.tellg(), std::fstream::pos_type(size)); +} + +std::shared_ptr create_multifile_torrent() +{ + // the two first files are exactly the size of a piece + static std::array const file_sizes{{ 0x40000, 0x40000, 4300, 0, 400, 4300, 6, 4}}; + + lt::file_storage fs; + create_random_files("test_torrent_dir", file_sizes, &fs); + // the torrent needs to be v1 only because the zero_priority_missing_partfile + // test relies on non-aligned files + lt::create_torrent t(fs, 0x40000, lt::create_torrent::v1_only); + + // calculate the hash for all pieces + set_piece_hashes(t, "."); + + std::vector buf; + lt::bencode(std::back_inserter(buf), t.generate()); + return std::make_shared(buf, lt::from_span); +} + +TORRENT_TEST(aligned_zero_priority) +{ + run_test( + [&](lt::add_torrent_params& atp, lt::settings_pack&) { + atp.file_priorities.push_back(lt::download_priority_t{1}); + atp.file_priorities.push_back(lt::download_priority_t{0}); + atp.ti = create_multifile_torrent(); + atp.save_path = "."; + }, + [](lt::session& ses) { + std::vector tor = ses.get_torrents(); + TEST_EQUAL(tor.size(), 1); + TEST_EQUAL(tor[0].status().is_finished, true); + } + ); +} + +// we have a zero-priority file that also does not exist on disk. It does not +// overlap any piece in another file, so we don't need a partfile +TORRENT_TEST(aligned_zero_priority_no_file) +{ + std::string partfile; + run_test( + [&](lt::add_torrent_params& atp, lt::settings_pack&) { + atp.ti = create_multifile_torrent(); + atp.save_path = "."; + atp.file_priorities.push_back(lt::download_priority_t{1}); + atp.file_priorities.push_back(lt::download_priority_t{0}); + std::string filename = lt::combine_path(lt::current_working_directory() + , lt::combine_path(atp.save_path, atp.ti->files().file_path(lt::file_index_t{1}))); + partfile = lt::combine_path(lt::current_working_directory() + , lt::combine_path(atp.save_path, "." + lt::aux::to_hex(atp.ti->info_hashes().v1.to_string()) + ".parts")); + lt::error_code ec; + lt::remove(filename, ec); + TEST_CHECK(!ec); + }, + [](lt::session& ses) { + std::vector tor = ses.get_torrents(); + TEST_EQUAL(tor.size(), 1); + TEST_EQUAL(tor[0].status().is_finished, true); + } + ); + + // the part file should not have been created. There is no need for a + // partfile + lt::error_code ec; + lt::file_status fs; + stat_file(partfile, &fs, ec); + TEST_EQUAL(ec, boost::system::errc::no_such_file_or_directory); +} + +// we have a file whose priority is 0, we don't have the file on disk nor a +// part-file for it. The checking should complete and enter download state. +TORRENT_TEST(zero_priority_missing_partfile) +{ + std::shared_ptr ti = create_multifile_torrent(); + run_test( + [&](lt::add_torrent_params& atp, lt::settings_pack&) { + atp.ti = ti; + atp.save_path = "."; + atp.file_priorities.push_back(lt::download_priority_t{1}); + atp.file_priorities.push_back(lt::download_priority_t{1}); + atp.file_priorities.push_back(lt::download_priority_t{0}); + std::string const filename = lt::combine_path(lt::current_working_directory() + , lt::combine_path(atp.save_path, atp.ti->files().file_path(lt::file_index_t{2}))); + + std::cout << "removing: " << filename << "\n"; + lt::error_code ec; + lt::remove(filename, ec); + TEST_CHECK(!ec); + }, + [&](lt::session& ses) { + std::vector tor = ses.get_torrents(); + TEST_EQUAL(tor.size(), 1); + TEST_EQUAL(tor[0].status().num_pieces, ti->num_pieces() - 1); + TEST_EQUAL(tor[0].status().is_finished, false); + } + ); +} + +TORRENT_TEST(checking) +{ + run_test( + [](lt::add_torrent_params& atp, lt::settings_pack& p) { + atp.flags |= lt::torrent_flags::auto_managed; +#if TORRENT_ABI_VERSION == 1 + p.set_int(lt::settings_pack::cache_size, 100); +#endif + }, + [](lt::session& ses) { + + std::vector tor = ses.get_torrents(); + TEST_EQUAL(tor.size(), 1); + + TEST_EQUAL(tor[0].status().is_seeding, true); + }); +} + diff --git a/simulation/test_dht.cpp b/simulation/test_dht.cpp new file mode 100644 index 0000000..0d81bf1 --- /dev/null +++ b/simulation/test_dht.cpp @@ -0,0 +1,331 @@ +/* + +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 "settings.hpp" +#include "setup_dht.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/socket_io.hpp" +#include "setup_swarm.hpp" +#include "libtorrent/kademlia/ed25519.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/kademlia/item.hpp" +#include "libtorrent/kademlia/dht_state.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" + +using lt::settings_pack; + +#ifndef TORRENT_DISABLE_DHT +void bootstrap_session(std::vector networks, lt::session& ses) +{ + lt::dht::dht_state state; + + for (auto dht : networks) + { + // bootstrap off of 8 of the nodes + auto router_nodes = dht->router_nodes(); + if (lt::aux::is_v6(router_nodes.front())) + state.nodes6 = router_nodes; + else + state.nodes = router_nodes; + } + + ses.set_dht_state(std::move(state)); + settings_pack pack; + pack.set_bool(settings_pack::enable_dht, true); + pack.set_int(settings_pack::alert_mask, lt::alert_category::all); + pack.set_bool(settings_pack::dht_ignore_dark_internet, false); + pack.set_bool(settings_pack::dht_restrict_routing_ips, false); + ses.apply_settings(pack); +} +#endif // TORRENT_DISABLE_DHT + +TORRENT_TEST(dht_bootstrap) +{ +#ifndef TORRENT_DISABLE_DHT + sim::default_config cfg; + sim::simulation sim{cfg}; + + dht_network dht(sim, 3000); + + int routing_table_depth = 0; + int num_nodes = 0; + + setup_swarm(1, swarm_test::download, sim + // add session + , [](::settings_pack&) {} + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [&](lt::alert const* a, lt::session&) + { + if (lt::dht_stats_alert const* p = lt::alert_cast(a)) + { + routing_table_depth = std::max(int(p->routing_table.size()), routing_table_depth); + int c = 0; + for (auto const& b : p->routing_table) + { + c += b.num_nodes; + c += b.num_replacements; + } + num_nodes = std::max(c, num_nodes); + print_routing_table(p->routing_table); + } + else if (lt::session_stats_alert const* sa = lt::alert_cast(a)) + { + int const dht_nodes = lt::find_metric_idx("dht.nodes"); + TEST_CHECK(sa->counters()[dht_nodes] > 2); + } + } + // terminate? + , [&](int ticks, lt::session& ses) -> bool + { + if (ticks == 0) + { + bootstrap_session({&dht}, ses); + } + if (ticks > 500) + { + ses.post_session_stats(); + std::printf("depth: %d nodes: %d\n", routing_table_depth, num_nodes); + TEST_CHECK(routing_table_depth >= 8); + TEST_CHECK(num_nodes >= 50); + dht.stop(); + return true; + } + ses.post_dht_stats(); + return false; + }); + + sim.run(); + +#endif // TORRENT_DISABLE_DHT + +} + +TORRENT_TEST(dht_dual_stack_get_peers) +{ +#ifndef TORRENT_DISABLE_DHT + sim::default_config cfg; + sim::simulation sim{ cfg }; + + dht_network dht(sim, 100); + dht_network dht6(sim, 100, dht_network::bind_ipv6); + + lt::sha1_hash const test_ih("01234567890123456789"); + bool got_peer_v4 = false, got_peer_v6 = false; + + setup_swarm(1, swarm_test::download, sim + // add session + , [](lt::settings_pack&) { + } + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [&](lt::alert const* a, lt::session&) + { + if (lt::dht_get_peers_reply_alert const* p = lt::alert_cast(a)) + { + std::vector peers = p->peers(); + for (lt::tcp::endpoint const& peer : peers) + { + // TODO: verify that the endpoint matches the session's + got_peer_v4 |= lt::aux::is_v4(peer); + got_peer_v6 |= lt::aux::is_v6(peer); + } + } + } + // terminate? + , [&](int ticks, lt::session& ses) -> bool + { + if (ticks == 0) + { + bootstrap_session({&dht, &dht6}, ses); + } + if (ticks == 2) + { + ses.dht_announce(test_ih, 6881); + } + if (ticks == 4) + { + ses.dht_get_peers(test_ih); + } + if (ticks == 6) + { + TEST_CHECK(got_peer_v4); + TEST_CHECK(got_peer_v6); + return true; + } + return false; + }); + + sim.run(); + +#endif // TORRENT_DISABLE_DHT +} + +TORRENT_TEST(dht_dual_stack_immutable_item) +{ +#ifndef TORRENT_DISABLE_DHT + sim::default_config cfg; + sim::simulation sim{ cfg }; + + dht_network dht(sim, 100); + dht_network dht6(sim, 100, dht_network::bind_ipv6); + + lt::sha1_hash item_hash; + bool got_item = false; + + setup_swarm(1, swarm_test::download, sim + // add session + , [](lt::settings_pack&) { + } + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [&](lt::alert const* a, lt::session&) + { + if (lt::dht_immutable_item_alert const* p = lt::alert_cast(a)) + { + // we should only get one alert for each request + TEST_CHECK(!got_item); + got_item = p->target == item_hash && p->item.string() == "immutable item"; + } + } + // terminate? + , [&](int ticks, lt::session& ses) -> bool + { + if (ticks == 0) + { + bootstrap_session({&dht, &dht6}, ses); + } + if (ticks == 2) + { + item_hash = ses.dht_put_item(lt::entry("immutable item")); + } + if (ticks == 4) + { + ses.dht_get_item(item_hash); + } + if (ticks == 6) + { + TEST_CHECK(got_item); + return true; + } + return false; + }); + + sim.run(); + +#endif // TORRENT_DISABLE_DHT +} + +TORRENT_TEST(dht_dual_stack_mutable_item) +{ +#ifndef TORRENT_DISABLE_DHT + sim::default_config cfg; + sim::simulation sim{ cfg }; + + dht_network dht(sim, 100); + dht_network dht6(sim, 100, dht_network::bind_ipv6); + + lt::dht::secret_key sk; + lt::dht::public_key pk; + int put_count = 0; + bool got_item = false; + + setup_swarm(1, swarm_test::download, sim + // add session + , [](lt::settings_pack&) { + } + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [&](lt::alert const* a, lt::session&) + { + if (lt::dht_mutable_item_alert const* p = lt::alert_cast(a)) + { + TEST_CHECK(!got_item); + if (p->authoritative) + got_item = p->key == pk.bytes && p->item.string() == "mutable item"; + } + } + // terminate? + , [&](int ticks, lt::session& ses) -> bool + { + if (ticks == 0) + { + bootstrap_session({&dht, &dht6}, ses); + } + if (ticks == 2) + { + std::array seed; + std::tie(pk, sk) = lt::dht::ed25519_create_keypair(seed); + + ses.dht_put_item(pk.bytes, [&](lt::entry& item, std::array& sig + , std::int64_t& seq, std::string const& salt) + { + item = "mutable item"; + seq = 1; + std::vector v; + lt::bencode(std::back_inserter(v), item); + lt::dht::signature sign = lt::dht::sign_mutable_item(v, salt + , lt::dht::sequence_number(seq), pk, sk); + put_count++; + sig = sign.bytes; + }); + } + if (ticks == 4) + { + // should be one for each stack, ipv4 and ipv6 + TEST_EQUAL(put_count, 2); + ses.dht_get_item(pk.bytes); + } + if (ticks == 6) + { + TEST_CHECK(got_item); + return true; + } + return false; + }); + + sim.run(); + +#endif // TORRENT_DISABLE_DHT +} diff --git a/simulation/test_dht_bootstrap.cpp b/simulation/test_dht_bootstrap.cpp new file mode 100644 index 0000000..e9b86ac --- /dev/null +++ b/simulation/test_dht_bootstrap.cpp @@ -0,0 +1,112 @@ +/* + +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 "simulator/simulator.hpp" +#include "utils.hpp" +#include "fake_peer.hpp" // for fake_node +#include "libtorrent/time.hpp" +#include "settings.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "setup_transfer.hpp" // for addr() + +using namespace sim; + +#ifndef TORRENT_DISABLE_DHT + +struct sim_config : sim::default_config +{ + chrono::high_resolution_clock::duration hostname_lookup( + asio::ip::address const& requestor + , std::string hostname + , std::vector& result + , boost::system::error_code& ec) + { + if (hostname == "dht.libtorrent.org") + { + result.push_back(addr("10.0.0.10")); + return lt::duration_cast(chrono::milliseconds(100)); + } + return default_config::hostname_lookup(requestor, hostname, result, ec); + } +}; + +TORRENT_TEST(dht_bootstrap) +{ + using sim::asio::ip::address_v4; + sim_config network_cfg; + sim::simulation sim{network_cfg}; + + std::vector zombies; + + fake_node node(sim, "10.0.0.10", 25401); + + lt::settings_pack pack; + // we use 0 threads (disk I/O operations will be performed in the network + // thread) to be simulator friendly. + pack.set_int(lt::settings_pack::aio_threads, 0); + pack.set_int(lt::settings_pack::hashing_threads, 0); + pack.set_bool(lt::settings_pack::enable_lsd, false); + pack.set_bool(lt::settings_pack::enable_upnp, false); + pack.set_bool(lt::settings_pack::enable_natpmp, false); + pack.set_bool(lt::settings_pack::enable_dht, true); + sim::asio::io_context ios(sim, addr("10.0.0.1")); + std::shared_ptr ses = std::make_shared(pack, ios); + + lt::deadline_timer timer(ios); + timer.expires_after(lt::seconds(10)); + timer.async_wait([&](lt::error_code const&) { + zombies.push_back(ses->abort()); + node.close(); + ses.reset(); + }); + + print_alerts(*ses); + + sim.run(); + + TEST_EQUAL(node.tripped(), true); + + std::vector const& p = node.incoming_packets().front(); + lt::bdecode_node n; + boost::system::error_code err; + int const ret = bdecode(p.data(), p.data() + p.size() + , n, err, nullptr, 10, 200); + TEST_EQUAL(ret, 0); + + lt::bdecode_node a = n.dict_find_dict("a"); + TEST_CHECK(a.dict_find_int_value("bs", -1) == 1); +} + +#else +TORRENT_TEST(disabled) {} +#endif // TORRENT_DISABLE_DHT diff --git a/simulation/test_dht_rate_limit.cpp b/simulation/test_dht_rate_limit.cpp new file mode 100644 index 0000000..d76691a --- /dev/null +++ b/simulation/test_dht_rate_limit.cpp @@ -0,0 +1,271 @@ +/* + +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" + +#include "simulator/simulator.hpp" + +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/kademlia/dht_state.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" + +#include +#include +#include + +using namespace lt; +using namespace sim; +using namespace std::placeholders; + +#if !defined TORRENT_DISABLE_DHT + +struct obs : dht::dht_observer +{ + void set_external_address(lt::aux::listen_socket_handle const&, address const& /* addr */ + , address const& /* source */) override + {} + int get_listen_port(lt::aux::transport, lt::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& /* ep */) override {} + void announce(sha1_hash const& /* ih */ + , address const& /* addr */, int /* port */) override {} + bool on_dht_request(string_view /* query */ + , dht::msg const& /* request */, entry& /* response */) override + { return false; } + +#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); + vprintf(fmt, v); + va_end(v); + puts("\n"); + } + void log_packet(message_direction_t /* dir */ + , span /* pkt */ + , udp::endpoint const& /* node */) override {} +#endif +}; + +void send_packet(lt::udp_socket& sock, lt::aux::listen_socket_handle const&, udp::endpoint const& ep + , span p, error_code& ec, udp_send_flags_t const flags) +{ + sock.send(ep, p, ec, flags); +} + +#endif // #if !defined TORRENT_DISABLE_DHT + +TORRENT_TEST(dht_rate_limit) +{ +#if !defined TORRENT_DISABLE_DHT + + default_config cfg; + simulation sim(cfg); + asio::io_context dht_ios(sim, make_address_v4("40.30.20.10")); + + // receiver (the DHT under test) + lt::udp_socket sock(dht_ios, lt::aux::listen_socket_handle{}); + obs o; + auto ls = std::make_shared(); + ls->external_address.cast_vote(make_address_v4("40.30.20.10") + , lt::aux::session_interface::source_dht, lt::address()); + ls->local_endpoint = tcp::endpoint(make_address_v4("40.30.20.10"), 8888); + error_code ec; + sock.bind(udp::endpoint(make_address_v4("40.30.20.10"), 8888), ec); + lt::aux::session_settings sett; + sett.set_int(settings_pack::dht_block_ratelimit, 100000); // disable the DOS blocker + sett.set_bool(settings_pack::dht_ignore_dark_internet, false); + sett.set_int(settings_pack::dht_upload_rate_limit, 400); + float const target_upload_rate = 400; + int const num_packets = 2000; + + counters cnt; + dht::dht_state state; + std::unique_ptr dht_storage(dht::dht_default_storage_constructor(sett)); + auto dht = std::make_shared( + &o, dht_ios, std::bind(&send_packet, std::ref(sock), _1, _2, _3, _4, _5) + , sett, cnt, *dht_storage, std::move(state)); + dht->new_socket(ls); + + bool stop = false; + std::function on_read + = [&](error_code const& ec) + { + if (ec) return; + udp_socket::packet p; + error_code err; + int const num = int(sock.read(lt::span(&p, 1), err)); + if (num) dht->incoming_packet(ls, p.from, p.data); + if (stop || err) return; + sock.async_read(on_read); + }; + sock.async_read(on_read); + + // sender + int num_packets_sent = 0; + asio::io_context sender_ios(sim, make_address_v4("10.20.30.40")); + udp::socket sender_sock(sender_ios); + sender_sock.open(udp::v4()); + sender_sock.bind(udp::endpoint(address_v4(), 4444)); + sender_sock.non_blocking(true); + asio::high_resolution_timer timer(sender_ios); + std::function sender_tick = [&](error_code const&) + { + if (num_packets_sent == num_packets) + { + // we're done. shut down (a second from now, to let the dust settle) + timer.expires_after(chrono::seconds(1)); + timer.async_wait([&](error_code const&) + { + dht->stop(); + stop = true; + sender_sock.close(); + sock.close(); + }); + return; + } + + char const packet[] = "d1:ad2:id20:ababababababababababe1:y1:q1:q4:pinge"; + sender_sock.send_to(asio::buffer(packet, sizeof(packet)-1) + , udp::endpoint(make_address_v4("40.30.20.10"), 8888)); + ++num_packets_sent; + + timer.expires_after(chrono::milliseconds(10)); + timer.async_wait(sender_tick); + }; + timer.expires_after(chrono::milliseconds(10)); + timer.async_wait(sender_tick); + + udp::endpoint from; + int num_bytes_received = 0; + int num_packets_received = 0; + char buffer[1500]; + std::function on_receive + = [&](error_code const& ec, std::size_t const bytes) + { + if (ec) return; + + num_bytes_received += int(bytes); + ++num_packets_received; + + sender_sock.async_receive_from(asio::buffer(buffer, sizeof(buffer)) + , from, on_receive); + }; + sender_sock.async_receive_from(asio::buffer(buffer, sizeof(buffer)) + , from, on_receive); + + // run simulation + lt::clock_type::time_point start = lt::clock_type::now(); + sim.run(); + lt::clock_type::time_point end = lt::clock_type::now(); + + // subtract one target_upload_rate here, since we initialize the quota to one + // full second worth of bandwidth + float const average_upload_rate = (num_bytes_received - target_upload_rate) + / (duration_cast(end - start).count() * 0.001f); + + std::printf("send %d packets. received %d packets (%d bytes). average rate: %f (target: %f)\n" + , num_packets_sent, num_packets_received, num_bytes_received + , average_upload_rate, target_upload_rate); + + // the actual upload rate should be within 5% of the target + TEST_CHECK(std::abs(average_upload_rate - target_upload_rate) < target_upload_rate * 0.05); + + TEST_EQUAL(cnt[counters::dht_messages_in], num_packets); + + // the number of dropped packets + the number of received pings, should equal + // exactly the number of packets we sent + TEST_EQUAL(cnt[counters::dht_messages_in_dropped] + + cnt[counters::dht_ping_in], num_packets); + +#endif // #if !defined TORRENT_DISABLE_DHT +} + +// TODO: put test here to take advantage of existing code, refactor +TORRENT_TEST(dht_delete_socket) +{ +#ifndef TORRENT_DISABLE_DHT + + sim::default_config cfg; + sim::simulation sim(cfg); + sim::asio::io_context dht_ios(sim, lt::make_address_v4("40.30.20.10")); + + lt::udp_socket sock(dht_ios, lt::aux::listen_socket_handle{}); + error_code ec; + sock.bind(udp::endpoint(make_address_v4("40.30.20.10"), 8888), ec); + + obs o; + auto ls = std::make_shared(); + ls->external_address.cast_vote(make_address_v4("40.30.20.10") + , lt::aux::session_interface::source_dht, lt::address()); + ls->local_endpoint = tcp::endpoint(make_address_v4("40.30.20.10"), 8888); + lt::aux::session_settings sett; + counters cnt; + dht::dht_state state; + std::unique_ptr dht_storage(dht::dht_default_storage_constructor(sett)); + auto dht = std::make_shared( + &o, dht_ios, std::bind(&send_packet, std::ref(sock), _1, _2, _3, _4, _5) + , sett, cnt, *dht_storage, std::move(state)); + + dht->start([](std::vector> const&){}); + dht->new_socket(ls); + + // schedule the removal of the socket at exactly 2 second, + // this simulates the fact that the internal scheduled call + // to connection_timeout will be executed right after leaving + // the state of cancellable + asio::high_resolution_timer t1(dht_ios); + t1.expires_after(chrono::seconds(2)); + t1.async_wait([&](error_code const&) + { + dht->delete_socket(ls); + }); + + // stop the DHT + asio::high_resolution_timer t2(dht_ios); + t2.expires_after(chrono::seconds(3)); + t2.async_wait([&](error_code const&) { dht->stop(); }); + + sim.run(); + +#endif // TORRENT_DISABLE_DHT +} diff --git a/simulation/test_dht_storage.cpp b/simulation/test_dht_storage.cpp new file mode 100644 index 0000000..9a8cdc6 --- /dev/null +++ b/simulation/test_dht_storage.cpp @@ -0,0 +1,219 @@ +/* + +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 "test.hpp" + +#ifndef TORRENT_DISABLE_DHT + +#include "settings.hpp" +#include "setup_transfer.hpp" // for ep() +#include "libtorrent/config.hpp" +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/kademlia/dht_settings.hpp" + +#include "libtorrent/io_context.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/aux_/time.hpp" + +#include "simulator/simulator.hpp" + +#include +#include + +using namespace lt; +using namespace lt::dht; +using namespace sim; +using sim::chrono::high_resolution_clock; +using namespace sim::asio; +using sim::simulation; +using sim::default_config; + +using namespace std::placeholders; + +namespace +{ + lt::aux::session_settings test_settings() + { + lt::aux::session_settings sett; + sett.set_int(settings_pack::dht_max_torrents, 2); + sett.set_int(settings_pack::dht_max_dht_items, 2); + sett.set_int(settings_pack::dht_item_lifetime, 120 * 60); + return sett; + } + + std::unique_ptr create_default_dht_storage( + settings_interface const& sett) + { + std::unique_ptr s(dht_default_storage_constructor(sett)); + TEST_CHECK(s.get() != nullptr); + + s->update_node_ids({to_hash("0000000000000000000000000000000000000200")}); + + return s; + } +} + +void timer_tick(dht_storage_interface* s + , dht_storage_counters const& c + , boost::system::error_code const&) +{ + s->tick(); + + TEST_EQUAL(s->counters().peers, c.peers); + TEST_EQUAL(s->counters().torrents, c.torrents); + TEST_EQUAL(s->counters().immutable_data, c.immutable_data); + TEST_EQUAL(s->counters().mutable_data, c.mutable_data); +} + +void test_expiration(simulation& sim + , high_resolution_clock::duration const& expiry_time + , std::unique_ptr& s + , dht_storage_counters const& c) +{ + sim::asio::io_context ios(sim, addr("10.0.0.1")); + + sim::asio::high_resolution_timer timer(ios); + timer.expires_after(expiry_time); + timer.async_wait(std::bind(&timer_tick, s.get(), c, _1)); + + sim.run(); +} + +TORRENT_TEST(dht_storage_counters) +{ + auto sett = test_settings(); + std::unique_ptr s(create_default_dht_storage(sett)); + + TEST_CHECK(s.get() != nullptr); + + 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"); + + 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); + s->announce_peer(n2, p2, "torrent_name1", false); + s->announce_peer(n2, p3, "torrent_name1", false); + s->announce_peer(n3, p4, "torrent_name2", false); + + s->put_immutable_item(n4, {"123", 3}, addr("124.31.75.21")); + 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")); + + dht::public_key pk; + dht::signature sig; + s->put_mutable_item(n4, {"123", 3}, sig, sequence_number(1), pk, {"salt", 4} + , addr("124.31.75.21")); + + dht_storage_counters c; + // note that we are using the aux global timer + + default_config cfg; + simulation sim(cfg); + + c.peers = 3; + c.torrents = 2; + c.immutable_data = 2; + c.mutable_data = 1; + test_expiration(sim, minutes(30), s, c); // test expiration of torrents and peers + + c.peers = 0; + c.torrents = 0; + c.immutable_data = 2; + c.mutable_data = 1; + test_expiration(sim, minutes(80), s, c); // test expiration of items before 2 hours + + c.peers = 0; + c.torrents = 0; + c.immutable_data = 0; + c.mutable_data = 0; + test_expiration(sim, hours(1), s, c); // test expiration of everything after 3 hours +} + +TORRENT_TEST(dht_storage_infohashes_sample) +{ + default_config cfg; + simulation sim(cfg); + sim::asio::io_context ios(sim, addr("10.0.0.1")); + + auto sett = test_settings(); + sett.set_int(settings_pack::dht_max_torrents, 5); + sett.set_int(settings_pack::dht_sample_infohashes_interval, 30); + sett.set_int(settings_pack::dht_max_infohashes_sample_count, 2); + std::unique_ptr s(create_default_dht_storage(sett)); + + TEST_CHECK(s.get() != nullptr); + + 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"); + + 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); + + sim::asio::high_resolution_timer timer(ios); + timer.expires_after(hours(1)); // expiration of torrents + timer.async_wait([&s](boost::system::error_code const&) + { + // tick here to trigger the torrents expiration + s->tick(); + + entry item; + int r = s->get_infohashes_sample(item); + TEST_EQUAL(r, 0); + }); + + sim.run(); +} +#else +TORRENT_TEST(disabled) {} +#endif // TORRENT_DISABLE_DHT diff --git a/simulation/test_error_handling.cpp b/simulation/test_error_handling.cpp new file mode 100644 index 0000000..493029f --- /dev/null +++ b/simulation/test_error_handling.cpp @@ -0,0 +1,250 @@ +/* + +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 "test.hpp" +#include "create_torrent.hpp" +#include "settings.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/settings_pack.hpp" +#include "simulator/simulator.hpp" +#include "simulator/socks_server.hpp" +#include "simulator/utils.hpp" +#include "setup_swarm.hpp" +#include "utils.hpp" +#include "setup_transfer.hpp" // for addr() + +using namespace sim; + +#if defined _MSC_VER && _ITERATOR_DEBUG_LEVEL > 0 +// https://developercommunity.visualstudio.com/content/problem/140200/c-stl-stdvector-constructor-declared-with-noexcept.html +#error "msvc's standard library does not support bad_alloc with debug iterators. This test only works with debug iterators disabled on msvc. _ITERATOR_DEBUG_LEVEL=0" +#endif + +int g_alloc_counter = 1000000; + +template +void run_test(int const round, HandleAlerts const& on_alert, Test const& test) +{ + using namespace lt; + + using asio::ip::address; + address const peer0 = addr("50.0.0.1"); + address const peer1 = addr("50.0.0.2"); + + // setup the simulation + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + sim::asio::io_context ios0 { sim, peer0 }; + sim::asio::io_context ios1 { sim, peer1 }; + + lt::session_proxy zombie[2]; + + // setup settings pack to use for the session (customization point) + lt::settings_pack pack = settings(); + + // disable utp by default + pack.set_bool(settings_pack::enable_outgoing_utp, false); + pack.set_bool(settings_pack::enable_incoming_utp, false); + + // disable encryption by default + 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_plaintext); + + pack.set_str(settings_pack::listen_interfaces, peer0.to_string() + ":6881"); + + std::shared_ptr ses[2]; + + // create session + ses[0] = std::make_shared(pack, ios0); + + pack.set_str(settings_pack::listen_interfaces, peer1.to_string() + ":6881"); + ses[1] = std::make_shared(pack, ios1); + + // only monitor alerts for session 0 (the downloader) + print_alerts(*ses[0], [=](lt::session& ses, lt::alert const* a) { + if (auto ta = alert_cast(a)) + { + ta->handle.connect_peer(lt::tcp::endpoint(peer1, 6881)); + } + on_alert(ses, a); + }); + + print_alerts(*ses[1]); + + // the first peer is a downloader, the second peer is a seed + lt::add_torrent_params params = ::create_torrent(1); + params.flags &= ~lt::torrent_flags::auto_managed; + params.flags &= ~lt::torrent_flags::paused; + + params.save_path = save_path(0); + ses[0]->async_add_torrent(params); + + params.save_path = save_path(1); + ses[1]->async_add_torrent(params); + + sim::timer t(sim, lt::seconds(60), [&](boost::system::error_code const&) + { + test(ses); + + // shut down + int idx = 0; + for (auto& s : ses) + { + zombie[idx++] = s->abort(); + s.reset(); + } + }); + + // we're only interested in allocation failures after construction has + // completed + g_alloc_counter = round; + sim.run(); +} + +#ifdef _MSC_VER +namespace libtorrent { +namespace aux { + // this is unfortunate. Some MSVC standard containers really don't move + // with noexcept, by actually allocating memory (i.e. it's not just a matter + // of accidentally missing noexcept specifiers). The heterogeneous queue used + // by the alert manager requires alerts to be noexcept move constructable and + // move assignable, which they claim to be, even though on MSVC some of them + // aren't. Things will improve in C++17 and it doesn't seem worth the trouble + // to make the heterogeneous queue support throwing moves, nor to replace all + // standard types with variants that can move noexcept. + // this thread local variable is set to true whenever such throwing + // container is wrapped, to make it pretend that it cannot throw. This + // signals to the test that it can't exercise that failure path. + // this is defined in simulation/utils.cpp + // TODO: in C++17, make this inline + extern thread_local int g_must_not_fail; +} +} +#endif + +void* operator new(std::size_t sz) +{ + if (--g_alloc_counter == 0) + { + char stack[10000]; + libtorrent::print_backtrace(stack, sizeof(stack), 40, nullptr); +#ifdef _MSC_VER + if (libtorrent::aux::g_must_not_fail) + { + ++g_alloc_counter; + return std::malloc(sz); + } +#endif + std::printf("\n\nthrowing bad_alloc (as part of test)\n%s\n\n\n", stack); + throw std::bad_alloc(); + } + return std::malloc(sz); +} + +void operator delete(void* ptr) noexcept +{ + std::free(ptr); +} + +void operator delete(void* ptr, std::size_t) noexcept +{ + std::free(ptr); +} + +TORRENT_TEST(error_handling) +{ + for (int i = 0; i < 8000; ++i) + { + // this will clear the history of all output we've printed so far. + // if we encounter an error from now on, we'll only print the relevant + // iteration + unit_test::reset_output(); + + // re-seed the random engine each iteration, to make the runs + // deterministic + lt::aux::random_engine().seed(0x82daf973); + + std::printf("\n\n === ROUND %d ===\n\n", i); + try + { + using namespace lt; + run_test(i, + [](lt::session&, lt::alert const*) {}, + [](std::shared_ptr[2]) {} + ); + } + catch (std::bad_alloc const&) + { + // this is kind of expected + } + catch (boost::system::system_error const& err) + { + TEST_ERROR("session constructor terminated with unexpected exception. \"" + + err.code().message() + "\" round: " + + std::to_string(i)); + break; + } + catch (std::exception const& err) + { + TEST_ERROR("session constructor terminated with unexpected exception. \"" + + std::string(err.what()) + "\" round: " + + std::to_string(i)); + break; + } + catch (...) + { + TEST_ERROR("session constructor terminated with unexpected exception. round: " + + std::to_string(i)); + break; + } + // if we didn't fail any allocations this run, there's no need to + // continue, we won't exercise any new code paths + if (g_alloc_counter > 0) break; + } + + // if this fails, we need to raise the limit in the loop above + TEST_CHECK(g_alloc_counter > 0); + + // we don't want any part of the actual test framework to suffer from failed + // allocations, so bump the counter + g_alloc_counter = 1000000; +} + diff --git a/simulation/test_fast_extensions.cpp b/simulation/test_fast_extensions.cpp new file mode 100644 index 0000000..eae3ae3 --- /dev/null +++ b/simulation/test_fast_extensions.cpp @@ -0,0 +1,360 @@ +/* + +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 "utils.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "create_torrent.hpp" +#include "settings.hpp" +#include "fake_peer.hpp" +#include "setup_transfer.hpp" // for ep() +#include "simulator/utils.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/random.hpp" + +using namespace lt::literals; + +template +void run_fake_peer_test( + lt::add_torrent_params params + , Sett const& sett + , Alert const& alert) +{ + sim::default_config cfg; + sim::simulation sim{cfg}; + + sim::asio::io_context ios(sim, lt::make_address_v4("50.0.0.1")); + lt::session_proxy zombie; + + // setup settings pack to use for the session (customization point) + lt::settings_pack pack = settings(); + pack.set_str(lt::settings_pack::listen_interfaces, "0.0.0.0:6881"); + sett(pack); + // create session + std::shared_ptr ses = std::make_shared(pack, ios); + + fake_peer p1(sim, "60.0.0.0"); + + params.flags &= ~lt::torrent_flags::auto_managed; + params.flags &= ~lt::torrent_flags::paused; + ses->async_add_torrent(params); + + // the alert notification function is called from within libtorrent's + // context. It's not OK to talk to libtorrent in there, post it back out and + // then ask for alerts. + print_alerts(*ses, [&](lt::session& ses, lt::alert const* a) { + alert(ses, a, p1); + }); + + sim::timer t(sim, lt::seconds(1) + , [&](boost::system::error_code const&) + { + // shut down + zombie = ses->abort(); + + p1.close(); + + ses.reset(); + }); + + sim.run(); +} + +struct idle_peer +{ + idle_peer(simulation& sim, char const* ip) + : m_ios(sim, lt::make_address(ip)) + { + boost::system::error_code ec; + m_acceptor.open(asio::ip::tcp::v4(), ec); + TEST_CHECK(!ec); + m_acceptor.bind(asio::ip::tcp::endpoint(asio::ip::address_v4::any(), 6881), ec); + TEST_CHECK(!ec); + m_acceptor.listen(10, ec); + TEST_CHECK(!ec); + + m_acceptor.async_accept(m_socket, [&] (boost::system::error_code const& ec) + { + m_accepted = true; + + if (!m_handshake) return; + + static char handshake_buffer[68]; + + asio::async_read(m_socket, asio::buffer(handshake_buffer, 68) + , [&](boost::system::error_code const& ec, std::size_t) + { + if (memcmp(handshake_buffer, "\x13" "BitTorrent protocol", 20) != 0) + { + std::printf(" invalid protocol specifier\n"); + m_socket.close(); + return; + } + + // change the peer ID and echo back the handshake + lt::aux::random_bytes({handshake_buffer + 48, 20}); + asio::async_write(m_socket, asio::buffer(handshake_buffer, 68) + , [](boost::system::error_code const& ec, size_t) { }); + }); + }); + } + + void enable_handshake() { m_handshake = true; } + + void close() + { + m_acceptor.close(); + m_socket.close(); + } + + + bool accepted() const { return m_accepted; } + + asio::io_context m_ios; + asio::ip::tcp::acceptor m_acceptor{m_ios}; + asio::ip::tcp::socket m_socket{m_ios}; + + bool m_accepted = false; + bool m_handshake = false; +}; + +lt::time_duration run_timeout_sim(sim::simulation& sim) +{ + sim::asio::io_context ios(sim, lt::make_address_v4("50.0.0.1")); + lt::session_proxy zombie; + + // setup settings pack to use for the session (customization point) + lt::settings_pack pack = settings(); + pack.set_str(lt::settings_pack::listen_interfaces, "0.0.0.0:6881"); + pack.set_bool(lt::settings_pack::enable_outgoing_utp, false); + pack.set_bool(lt::settings_pack::enable_incoming_utp, false); + pack.set_int(lt::settings_pack::alert_mask, lt::alert_category::error + | lt::alert_category::connect + | lt::alert_category::peer_log); + + // create session + std::shared_ptr ses = std::make_shared(pack, ios); + + int const num_pieces = 5; + lt::add_torrent_params params = create_torrent(0, false, num_pieces); + params.flags &= ~lt::torrent_flags::auto_managed; + params.flags &= ~lt::torrent_flags::paused; + ses->async_add_torrent(params); + + lt::time_point peer_timeout_timestamp{}; + lt::time_point const start = lt::clock_type::now(); + + // the alert notification function is called from within libtorrent's + // context. It's not OK to talk to libtorrent in there, post it back out and + // then ask for alerts. + print_alerts(*ses, [&](lt::session& ses, lt::alert const* a) { + + if (auto at = lt::alert_cast(a)) + { + lt::torrent_handle h = at->handle; + h.connect_peer(ep("60.0.0.0", 6881)); + } + else if (auto pe = lt::alert_cast(a)) + { + if (peer_timeout_timestamp == lt::time_point{}) + peer_timeout_timestamp = pe->timestamp(); + } + + }); + + sim::timer t(sim, lt::seconds(300) + , [&](boost::system::error_code const&) + { + // shut down + zombie = ses->abort(); + ses.reset(); + }); + + sim.run(); + + TEST_CHECK(peer_timeout_timestamp != lt::time_point{}); + return peer_timeout_timestamp - start; +} + +#ifndef TORRENT_DISABLE_LOGGING +// make sure we consistently send the same allow-fast pieces, regardless +// of which pieces the peer has. +TORRENT_TEST(allow_fast) +{ + std::set allowed_fast; + + int const num_pieces = 50; + lt::add_torrent_params params = create_torrent(0, false, num_pieces); + std::vector bitfield(num_pieces, false); + + for (int i = 0; i < num_pieces + 1; ++i) + { + // just for this one session, to check for duplicates + std::set local_allowed_fast; + + run_fake_peer_test(params, [] (lt::settings_pack& pack) { + pack.set_int(lt::settings_pack::allowed_fast_set_size, 13); + } + , [&] (lt::session&, lt::alert const* a, fake_peer& p1) + { + if (auto at = lt::alert_cast(a)) + { + lt::torrent_handle h = at->handle; + p1.connect_to(ep("50.0.0.1", 6881) + , h.torrent_file()->info_hashes().v1); + p1.send_bitfield(bitfield); + p1.send_interested(); + } + else if (auto l = lt::alert_cast(a)) + { + if (l->event_type != "ALLOWED_FAST"_sv) return; + + int const piece = atoi(l->log_message()); + // make sure we don't get the same allowed piece more than once + TEST_EQUAL(local_allowed_fast.count(piece), 0); + + // build the union of all allow-fast pieces we've received, across + // simulations. + allowed_fast.insert(piece); + local_allowed_fast.insert(piece); + + // make sure this is a valid piece + TEST_CHECK(piece < num_pieces); + TEST_CHECK(piece >= 0); + // and make sure it's not one of the pieces we have + // because that would be redundant + TEST_EQUAL(bitfield[piece], false); + } + }); + + // i goes from [0, mum_pieces + 1) to cover the have-none and have-all + // cases. After the last iteration, we can't add another piece. + if (i < int(bitfield.size())) + bitfield[i] = true; + } + + // we should never have sent any other pieces than the 13 designated for this + // peer's IP. + TEST_EQUAL(int(allowed_fast.size()), 13); +} + +// This tests a worst case scenario of allow-fast configuration where we must +// verify that libtorrent correctly aborts before satisfying the settings +// (because doing so would be too expensive) +// +// we have a torrent with a lot of pieces, and we want to send that many minus +// one allow-fast pieces. The way allow-fast pieces are computed is by hashing +// the peer's IP modulus the number of pieces. To actually compute which pieces +// to send (or which one piece _not_ to send) we would have to work hard through +// a lot of duplicates. This test makes sure we don't, and abort well before +// then +TORRENT_TEST(allow_fast_stress) +{ + std::set allowed_fast; + + int const num_pieces = 50000; + lt::add_torrent_params params = create_torrent(0, false, num_pieces); + + run_fake_peer_test(params, [&] (lt::settings_pack& pack) { + pack.set_int(lt::settings_pack::allowed_fast_set_size, num_pieces - 1); + } + , [&] (lt::session&, lt::alert const* a, fake_peer& p1) + { + if (auto at = lt::alert_cast(a)) + { + lt::torrent_handle h = at->handle; + p1.connect_to(ep("50.0.0.1", 6881) + , h.torrent_file()->info_hashes().v1); + p1.send_interested(); + } + else if (auto l = lt::alert_cast(a)) + { + if (l->event_type != "ALLOWED_FAST"_sv) return; + + int const piece = atoi(l->log_message()); + + // make sure we don't get the same allowed piece more than once + TEST_EQUAL(allowed_fast.count(piece), 0); + + // build the union of all allow-fast pieces we've received, across + // simulations. + allowed_fast.insert(piece); + + // make sure this is a valid piece + TEST_CHECK(piece < num_pieces); + TEST_CHECK(piece >= 0); + } + }); + + std::printf("received %d allowed fast, out of %d configured ones\n" + , int(allowed_fast.size()), num_pieces - 1); + TEST_CHECK(int(allowed_fast.size()) < num_pieces / 80); +} + +#endif + +TORRENT_TEST(peer_idle_timeout) +{ + sim::default_config cfg; + sim::simulation sim{cfg}; + + // just a listen socket that accepts connections, and just respond with a + // bittorrent handshake, but nothing more + idle_peer peer(sim, "60.0.0.0"); + peer.enable_handshake(); + + auto peer_timeout_timestamp = run_timeout_sim(sim); + + // the peer timeout defaults to 120 seconds + // settings_pack::peer_timeout + TEST_CHECK(peer_timeout_timestamp < lt::seconds(122)); + TEST_CHECK(peer_timeout_timestamp > lt::seconds(120)); +} + +TORRENT_TEST(handshake_timeout) +{ + sim::default_config cfg; + sim::simulation sim{cfg}; + + // just a listen socket that accepts connections, but never responds + idle_peer peer(sim, "60.0.0.0"); + + auto peer_timeout_timestamp = run_timeout_sim(sim); + + // the handshake timeout defaults to 10 seconds + // settings_pack::handshake_timeout + TEST_CHECK(peer_timeout_timestamp < lt::seconds(15)); + TEST_CHECK(peer_timeout_timestamp > lt::seconds(9)); +} diff --git a/simulation/test_file_pool.cpp b/simulation/test_file_pool.cpp new file mode 100644 index 0000000..94a797b --- /dev/null +++ b/simulation/test_file_pool.cpp @@ -0,0 +1,138 @@ +/* + +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 "setup_swarm.hpp" +#include "test.hpp" +#include "utils.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/disk_interface.hpp" + +using namespace lt; + +// the disk I/O thread is not simulated with high enough fidelity for this to +// work +TORRENT_TEST(close_file_interval) +{ + bool ran_to_completion = false; + + // with seed mode + setup_swarm(2, swarm_test::download + // add session + , [](lt::settings_pack& pack) { + pack.set_int(settings_pack::close_file_interval, 20); + } + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [](lt::alert const* a, lt::session& ses) {} + // terminate + , [&](int ticks, lt::session& ses) -> bool + { + // terminate after 40 seconds + if (ticks > 24) + { + ran_to_completion = true; + return true; + } + + torrent_handle h = ses.get_torrents().front(); + std::vector const file_status = h.file_status(); + printf("%d: %d files\n", ticks, int(file_status.size())); + if (ticks > 0 && ticks < 19) + { + TEST_EQUAL(file_status.size(), 1); + } + else if (ticks > 21) + { + // the close file timer shuold have kicked in at 20 seconds + // and closed the file + TEST_EQUAL(file_status.size(), 0); + } + return false; + }); + TEST_CHECK(ran_to_completion); +} + +TORRENT_TEST(file_pool_size) +{ + bool ran_to_completion = false; + int max_files = 0; + + setup_swarm(2, swarm_test::download + // add session + , [](lt::settings_pack& pack) + { + pack.set_int(lt::settings_pack::file_pool_size, 5); + } + // add torrent + , [](lt::add_torrent_params& atp) { + // we need a torrent with lots of files in it, to hit the + // file_size_limit we set. + file_storage fs; + for (int i = 0; i < 0x10 * 9; ++i) + { + char filename[50]; + snprintf(filename, sizeof(filename), "root/file-%d", i); + fs.add_file(filename, 0x400); + } + atp.ti = std::make_shared(*atp.ti); + atp.ti->remap_files(fs); + } + // on alert + , [&](lt::alert const*, lt::session&) {} + // terminate + , [&](int ticks, lt::session& ses) -> bool + { + if (ticks > 80) + { + TEST_ERROR("timeout"); + return true; + } + + std::vector const status = ses.get_torrents().at(0).file_status(); + printf("open files: %d\n", int(status.size())); + max_files = std::max(max_files, int(status.size())); + if (!is_seed(ses)) return false; + printf("completed in %d ticks\n", ticks); + ran_to_completion = true; + return true; + }); + + TEST_CHECK(max_files <= 5); + TEST_CHECK(max_files >= 4); + TEST_CHECK(ran_to_completion); +} + diff --git a/simulation/test_http_connection.cpp b/simulation/test_http_connection.cpp new file mode 100644 index 0000000..6497895 --- /dev/null +++ b/simulation/test_http_connection.cpp @@ -0,0 +1,663 @@ +/* + +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 "test.hpp" +#include "settings.hpp" +#include "setup_swarm.hpp" +#include "simulator/simulator.hpp" +#include "simulator/http_server.hpp" +#include "simulator/http_proxy.hpp" +#include "simulator/socks_server.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/http_connection.hpp" +#include "libtorrent/aux_/resolver.hpp" +#include "libtorrent/random.hpp" + +#include "make_proxy_settings.hpp" + +#include +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +using namespace lt; +using namespace sim; + +using chrono::duration_cast; + +struct sim_config : sim::default_config +{ + chrono::high_resolution_clock::duration hostname_lookup( + asio::ip::address const& requestor + , std::string hostname + , std::vector& result + , boost::system::error_code& ec) override + { + if (hostname == "try-next.com") + { + result.push_back(make_address_v4("10.0.0.10")); + result.push_back(make_address_v4("10.0.0.9")); + result.push_back(make_address_v4("10.0.0.8")); + result.push_back(make_address_v4("10.0.0.7")); + result.push_back(make_address_v4("10.0.0.6")); + result.push_back(make_address_v4("10.0.0.5")); + result.push_back(make_address_v4("10.0.0.4")); + result.push_back(make_address_v4("10.0.0.3")); + + // this is the IP that works, all other should fail + result.push_back(make_address_v4("10.0.0.2")); + return duration_cast(chrono::milliseconds(100)); + } + + if (hostname == "test-hostname.com") + { + result.push_back(make_address_v4("10.0.0.2")); + return duration_cast(chrono::milliseconds(100)); + } + + if (hostname == "dual-stack.test-hostname.com") + { + result.push_back(make_address_v4("10.0.0.2")); + result.push_back(make_address_v6("ff::dead:beef")); + return duration_cast(chrono::milliseconds(100)); + } + + return default_config::hostname_lookup(requestor, hostname, result, ec); + } +}; + +// takes a string of data and chunks it up using HTTP chunked encoding +std::string chunk_string(std::string s) +{ + size_t i = 10; + std::string ret; + while (!s.empty()) + { + i = std::min(i, s.size()); + char header[50]; + std::snprintf(header, sizeof(header), "%x\r\n", int(i)); + ret += header; + ret += s.substr(0, i); + s.erase(s.begin(), s.begin() + i); + i *= 2; + } + ret += "0\r\n\r\n"; + return ret; +} + +std::shared_ptr test_request(io_context& ios + , lt::aux::resolver& res + , std::string const& url + , char const* expected_data + , int const expected_size + , int const expected_status + , error_condition expected_error + , lt::aux::proxy_settings const& ps + , int* connect_handler_called + , int* handler_called + , std::string const& auth = std::string()) +{ + std::printf(" ===== TESTING: %s =====\n", url.c_str()); + +#if TORRENT_USE_SSL + ssl::context ssl_ctx(ssl::context::sslv23_client); + ssl_ctx.set_verify_mode(ssl::context::verify_none); +#endif + + auto h = std::make_shared(ios + , res + , [=](error_code const& ec, http_parser const& parser + , span data, http_connection&) + { + std::printf("RESPONSE: %s\n", url.c_str()); + ++*handler_called; + + // this is pretty gross. Since boost.asio is a header-only library, when this test is + // build against shared libraries of libtorrent and simulator, there will be multiple + // (distinct) error categories in boost.asio. The traditional comparison of error_code + // and error_condition may hence fail. + const bool error_ok = ec == expected_error + || (strcmp(ec.category().name(), expected_error.category().name()) == 0 + && ec.value() == expected_error.value()); + + if (!error_ok) + { + std::printf("ERROR: %s (expected: %s)\n" + , ec.message().c_str() + , expected_error.message().c_str()); + } + + const int http_status = parser.status_code(); + if (expected_size != -1) + { + TEST_EQUAL(int(data.size()), expected_size); + } + TEST_CHECK(error_ok); + if (expected_status != -1) + { + TEST_EQUAL(http_status, expected_status); + } + if (http_status == 200) + { + TEST_CHECK(expected_data + && int(data.size()) == expected_size + && memcmp(expected_data, data.data(), data.size()) == 0); + } + } + , true, 1024*1024 + , [=](http_connection& c) + { + ++*connect_handler_called; + TEST_CHECK(c.socket().is_open()); + std::printf("CONNECTED: %s\n", url.c_str()); + } + , lt::http_filter_handler() + , lt::hostname_filter_handler() +#if TORRENT_USE_SSL + , &ssl_ctx +#endif + ); + + h->get(url, seconds(1), 0, &ps, 5, "test/user-agent", boost::none + , lt::aux::resolver_flags{}, auth); + return h; +} + +void print_http_header(std::map const& headers) +{ + for (std::map::const_iterator i + = headers.begin(), end(headers.end()); i != end; ++i) + { + std::printf("%s: %s\n", i->first.c_str(), i->second.c_str()); + } +} + +void run_test(lt::aux::proxy_settings ps, std::string url, int expect_size, int expect_status + , boost::system::error_condition expect_error, std::vector expect_counters); + +enum expect_counters +{ + connect_handler = 0, + handler = 1, + test_file_req = 2, + redirect_req = 3, + rel_redirect_req = 4, + inf_redirect_req = 5, + chunked_req = 6, + test_file_gz_req = 7, + + num_counters +}; + +void run_suite(lt::aux::proxy_settings ps) +{ + std::string url_base = "http://10.0.0.2:8080"; + + run_test(ps, url_base + "/test_file", 1337, 200, error_condition(), { 1, 1, 1}); + + // positive test with a successful hostname + run_test(ps, "http://test-hostname.com:8080/test_file", 1337, 200, error_condition(), { 1, 1, 1}); + + run_test(ps, url_base + "/non-existent", 0, 404, error_condition(), { 1, 1 }); + run_test(ps, url_base + "/redirect", 1337, 200, error_condition(), { 2, 1, 1, 1 }); + run_test(ps, url_base + "/relative/redirect", 1337, 200, error_condition(), {2, 1, 1, 0, 1}); + + run_test(ps, url_base + "/infinite/redirect", 0, 301 + , error_condition(asio::error::eof, asio::error::get_misc_category()), {6, 1, 0, 0, 0, 6}); + + run_test(ps, url_base + "/chunked_encoding", 1337, 200, error_condition(), { 1, 1, 0, 0, 0, 0, 1}); + + // we are on an IPv4 host, we can't connect to IPv6 addresses, make sure that + // error is correctly propagated + // with socks5 we would be able to do this, assuming the socks server + // supported it, but the current socks implementation in libsimulator does + // not support IPv6 + if (ps.type != settings_pack::socks5 + && ps.type != settings_pack::http) + { + const auto expected_code = ps.type == settings_pack::socks4 ? + boost::system::errc::address_family_not_supported : + boost::system::errc::address_not_available; + + run_test(ps, "http://[ff::dead:beef]:8080/test_file", 0, -1 + , error_condition(expected_code, generic_category()) + , {0,1}); + } + + // there is no node at 10.0.0.10, this should fail with connection refused + if (ps.type != settings_pack::http) + { + run_test(ps, "http://10.0.0.10:8080/test_file", 0, -1, + error_condition(boost::system::errc::connection_refused, generic_category()) + , {0,1}); + } + else + { + run_test(ps, "http://10.0.0.10:8080/test_file", 0, 503, + error_condition(), {1,1}); + } + + // the try-next test in his case would test the socks proxy itself, whether + // it has robust retry behavior (which the simple test proxy that comes with + // libsimulator doesn't). + if (ps.proxy_hostnames == false) + { + // this hostname will resolve to multiple IPs, all but one that we cannot + // connect to and the second one where we'll get the test file response. Make + // sure the http_connection correcly tries the second IP if the first one + // fails. + run_test(ps, "http://try-next.com:8080/test_file", 1337, 200 + , error_condition(), { 1, 1, 1}); + } + + // the http proxy does not support hostname lookups yet + if (ps.type != settings_pack::http) + { + const error_condition expected_error = ps.proxy_hostnames + ? error_condition(boost::system::errc::host_unreachable, generic_category()) + : error_condition(asio::error::host_not_found, boost::asio::error::get_netdb_category()); + + // make sure hostname lookup failures are passed through correctly + run_test(ps, "http://non-existent.com/test_file", 0, -1 + , expected_error, { 0, 1 }); + } + + // make sure we handle gzipped content correctly + run_test(ps, url_base + "/test_file.gz", 1337, 200, error_condition(), { 1, 1, 0, 0, 0, 0, 0, 1}); + +// TODO: 2 test basic-auth +// TODO: 2 test https +} + +void run_test(lt::aux::proxy_settings ps, std::string url, int expect_size, int expect_status + , boost::system::error_condition expect_error, std::vector expect_counters) +{ + using sim::asio::ip::address_v4; + sim_config network_cfg; + sim::simulation sim{network_cfg}; + + // allow sparse expected counters + expect_counters.resize(num_counters, 0); + + sim::asio::io_context web_server(sim, make_address_v4("10.0.0.2")); + sim::asio::io_context ios(sim, make_address_v4("10.0.0.1")); + sim::asio::io_context proxy_ios(sim, make_address_v4("50.50.50.50")); + lt::aux::resolver res(ios); + + sim::http_server http(web_server, 8080); + sim::socks_server socks(proxy_ios, 4444, ps.type == settings_pack::socks4 ? 4 : 5); + sim::http_proxy http_p(proxy_ios, 4445); + + char data_buffer[4000]; + lt::aux::random_bytes(data_buffer); + + std::vector counters(num_counters, 0); + + http.register_handler("/test_file" + , [&data_buffer,&counters](std::string method, std::string req + , std::map& headers) + { + ++counters[test_file_req]; + print_http_header(headers); + TEST_EQUAL(method, "GET"); + return sim::send_response(200, "OK", 1337).append(data_buffer, 1337); + }); + + http.register_handler("/chunked_encoding" + , [&data_buffer,&counters](std::string method, std::string req + , std::map& headers) + { + ++counters[chunked_req]; + print_http_header(headers); + TEST_EQUAL(method, "GET"); + + // there's no content length with chunked encoding + return "HTTP/1.1 200 OK\r\nTransfer-encoding: Chunked\r\n\r\n" + + chunk_string(std::string(data_buffer, 1337)); + }); + + http.register_handler("/test_file.gz" + , [&data_buffer,&counters](std::string method, std::string req + , std::map& headers) + { + ++counters[test_file_gz_req]; + print_http_header(headers); + TEST_EQUAL(method, "GET"); + + char const* extra_headers[4] = {"Content-Encoding: gzip\r\n", "", "", ""}; + unsigned char const gzheader[] = { + 0x1f , 0x8b , 0x08 , 0x00 // ID, compression=deflate, flags=0 + , 0x00 , 0x00 , 0x00 , 0x00 // mtime=0 + , 0x00, 0x01 // extra headers, OS + , 0x01 // last block, uncompressed + , 0x39 , 0x05, 0xc6 , 0xfa // length = 1337 (little endian 16 bit and inverted) + }; + unsigned char trailer[8] = { 0, 0, 0, 0, 0x39, 0x05, 0x00, 0x00 }; + boost::crc_32_type crc; + crc.process_bytes(data_buffer, 1337); + std::uint32_t checksum = crc.checksum(); + trailer[0] = checksum >> 24; + trailer[1] = (checksum >> 16) & 0xff; + trailer[2] = (checksum >> 8) & 0xff; + trailer[3] = (checksum) & 0xff; + + std::string ret = sim::send_response(200, "OK", 1337 + sizeof(gzheader) + + sizeof(trailer), extra_headers); + ret.append(std::string((char const*)gzheader, sizeof(gzheader))); + ret.append(data_buffer, 1337); + ret.append(std::string((char const*)trailer, sizeof(trailer))); + return ret; + }); + + http.register_handler("/redirect" + , [&counters](std::string method, std::string req + , std::map&) + { + ++counters[redirect_req]; + TEST_EQUAL(method, "GET"); + return "HTTP/1.1 301 Moved Temporarily\r\n" + "Location: /test_file\r\n" + "\r\n"; + }); + + http.register_handler("/relative/redirect" + , [&counters](std::string method, std::string req + , std::map&) + { + ++counters[rel_redirect_req]; + TEST_EQUAL(method, "GET"); + return "HTTP/1.1 301 Moved Temporarily\r\n" + "Location: ../test_file\r\n" + "\r\n"; + }); + + http.register_handler("/infinite/redirect" + , [&counters](std::string method, std::string req + , std::map&) + { + ++counters[inf_redirect_req]; + TEST_EQUAL(method, "GET"); + return "HTTP/1.1 301 Moved Temporarily\r\n" + "Location: /infinite/redirect\r\n" + "\r\n"; + }); + + auto c = test_request(ios, res, url, data_buffer, expect_size + , expect_status, expect_error, ps, &counters[connect_handler] + , &counters[handler]); + + sim.run(); + + TEST_EQUAL(counters.size(), expect_counters.size()); + for (int i = 0; i < int(counters.size()); ++i) + { + if (counters[i] != expect_counters[i]) std::printf("i=%d\n", i); + TEST_EQUAL(counters[i], expect_counters[i]); + } +} + +TORRENT_TEST(http_connection) +{ + lt::aux::proxy_settings ps = make_proxy_settings(settings_pack::none); + run_suite(ps); +} + +TORRENT_TEST(http_connection_http) +{ + lt::aux::proxy_settings ps = make_proxy_settings(settings_pack::http); + ps.proxy_hostnames = true; + run_suite(ps); +} + +TORRENT_TEST(http_connection_socks4) +{ + lt::aux::proxy_settings ps = make_proxy_settings(settings_pack::socks4); + run_suite(ps); +} + +TORRENT_TEST(http_connection_socks5) +{ + lt::aux::proxy_settings ps = make_proxy_settings(settings_pack::socks5); + run_suite(ps); +} + +TORRENT_TEST(http_connection_socks5_proxy_names) +{ + lt::aux::proxy_settings ps = make_proxy_settings(settings_pack::socks5); + ps.proxy_hostnames = true; + run_suite(ps); +} + +// tests the error scenario of a http server listening on two sockets (ipv4/ipv6) which +// both accept the incoming connection but never send anything back. we test that +// both ip addresses get tried in turn and that the connection attempts time out as expected. +TORRENT_TEST(http_connection_timeout_server_stalls) +{ + sim_config network_cfg; + sim::simulation sim{network_cfg}; + // server has two ip addresses (ipv4/ipv6) + sim::asio::io_context server_ios(sim, make_address_v4("10.0.0.2")); + sim::asio::io_context server_ios_ipv6(sim, make_address_v6("ff::dead:beef")); + // same for client + sim::asio::io_context client_ios(sim, { + make_address_v4("10.0.0.1"), + make_address_v6("ff::abad:cafe") + }); + lt::aux::resolver resolver(client_ios); + + const unsigned short http_port = 8080; + sim::http_server http(server_ios, http_port); + sim::http_server http_ipv6(server_ios_ipv6, http_port); + + http.register_stall_handler("/timeout"); + http_ipv6.register_stall_handler("/timeout"); + + char data_buffer[4000]; + lt::aux::random_bytes(data_buffer); + + int connect_counter = 0; + int handler_counter = 0; + + error_condition timed_out(lt::errors::timed_out, lt::libtorrent_category()); + + auto c = test_request(client_ios, resolver + , "http://dual-stack.test-hostname.com:8080/timeout", data_buffer, -1, -1 + , timed_out, lt::aux::proxy_settings() + , &connect_counter, &handler_counter); + + sim.run(); + TEST_EQUAL(connect_counter, 2); // both endpoints are connected to + TEST_EQUAL(handler_counter, 1); // the handler only gets called once with error_code == timed_out +} + +// tests the error scenario of a http server listening on two sockets (ipv4/ipv6) neither of which +// accept incoming connections. we test that both ip addresses get tried in turn and that the +// connection attempts time out as expected. +TORRENT_TEST(http_connection_timeout_server_does_not_accept) +{ + sim_config network_cfg; + sim::simulation sim{network_cfg}; + // server has two ip addresses (ipv4/ipv6) + sim::asio::io_context server_ios(sim, { + make_address_v4("10.0.0.2"), + make_address_v6("ff::dead:beef") + }); + // same for client + sim::asio::io_context client_ios(sim, { + make_address_v4("10.0.0.1"), + make_address_v6("ff::abad:cafe") + }); + lt::aux::resolver resolver(client_ios); + + const unsigned short http_port = 8080; + + // listen on two sockets, but don't accept connections + asio::ip::tcp::acceptor server_socket_ipv4(server_ios); + server_socket_ipv4.open(tcp::v4()); + server_socket_ipv4.bind(tcp::endpoint(address_v4::any(), http_port)); + server_socket_ipv4.listen(); + + asio::ip::tcp::acceptor server_socket_ipv6(server_ios); + server_socket_ipv6.open(tcp::v6()); + server_socket_ipv6.bind(tcp::endpoint(address_v6::any(), http_port)); + server_socket_ipv6.listen(); + + int connect_counter = 0; + int handler_counter = 0; + + error_condition timed_out(lt::errors::timed_out, lt::libtorrent_category()); + + char data_buffer[4000]; + lt::aux::random_bytes(data_buffer); + + auto c = test_request(client_ios, resolver + , "http://dual-stack.test-hostname.com:8080/timeout_server_does_not_accept", data_buffer, -1, -1 + , timed_out, lt::aux::proxy_settings() + , &connect_counter, &handler_counter); + + sim.run(); + TEST_EQUAL(connect_counter, 0); // no connection takes place + TEST_EQUAL(handler_counter, 1); // the handler only gets called once with error_code == timed_out +} + +void test_proxy_failure(lt::settings_pack::proxy_type_t proxy_type) +{ + using sim::asio::ip::address_v4; + sim_config network_cfg; + sim::simulation sim{network_cfg}; + + sim::asio::io_context web_server(sim, make_address_v4("10.0.0.2")); + sim::asio::io_context ios(sim, make_address_v4("10.0.0.1")); + lt::aux::resolver res(ios); + + sim::http_server http(web_server, 8080); + + lt::aux::proxy_settings ps = make_proxy_settings(proxy_type); + + char data_buffer[4000]; + lt::aux::random_bytes(data_buffer); + + http.register_handler("/test_file" + , [&data_buffer](std::string method, std::string req + , std::map& headers) + { + print_http_header(headers); + // we're not supposed to get here + TEST_CHECK(false); + return sim::send_response(200, "OK", 1337).append(data_buffer, 1337); + }); + + int connect_counter = 0; + int handler_counter = 0; + auto c = test_request(ios, res, "http://10.0.0.2:8080/test_file" + , data_buffer, -1, -1 + , error_condition(boost::system::errc::connection_refused, boost::system::generic_category()) + , ps, &connect_counter, &handler_counter); + + sim.run(); +} + +// if we set up to user a proxy that does not exist, expect failure! +// if this doesn't fail, the other tests are invalid because the proxy may not +// be exercised! +TORRENT_TEST(http_connection_socks_error) +{ + test_proxy_failure(settings_pack::socks5); +} + +TORRENT_TEST(http_connection_http_error) +{ + test_proxy_failure(settings_pack::http); +} + +// Requests a proxied SSL connection. This test just ensures that the correct CONNECT request +// is sent to the proxy server. +TORRENT_TEST(http_connection_ssl_proxy) +{ + using sim::asio::ip::address_v4; + sim_config network_cfg; + sim::simulation sim{network_cfg}; + + sim::asio::io_context client_ios(sim, make_address_v4("10.0.0.1")); + sim::asio::io_context proxy_ios(sim, make_address_v4("50.50.50.50")); + lt::aux::resolver res(client_ios); + + sim::http_server http_proxy(proxy_ios, 4445); + + lt::aux::proxy_settings ps = make_proxy_settings(settings_pack::http); + + int client_counter = 0; + int proxy_counter = 0; + + http_proxy.register_handler("10.0.0.2:8080" + , [&proxy_counter](std::string method, std::string req, std::map&) + { + proxy_counter++; + TEST_EQUAL(method, "CONNECT"); + return sim::send_response(403, "Not supported", 1337); + }); + +#if TORRENT_USE_SSL + lt::ssl::context ssl_ctx(ssl::context::sslv23_client); + ssl_ctx.set_verify_mode(ssl::context::verify_none); +#endif + + auto h = std::make_shared(client_ios + , res + , [&client_counter](error_code const& ec, http_parser const& + , span, http_connection&) + { + client_counter++; + TEST_EQUAL(ec, boost::asio::error::operation_not_supported); + } + , true, 1024*1024, lt::http_connect_handler() + , http_filter_handler() + , hostname_filter_handler() +#if TORRENT_USE_SSL + , &ssl_ctx +#endif + ); + + h->start("10.0.0.2", 8080, seconds(1), 0, &ps, true /*ssl*/); + + sim.run(); + + TEST_EQUAL(client_counter, 1); + TEST_EQUAL(proxy_counter, 1); +} + +// TODO: test http proxy with password +// TODO: test socks5 with password +// TODO: test SSL +// TODO: test keepalive + diff --git a/simulation/test_ip_filter.cpp b/simulation/test_ip_filter.cpp new file mode 100644 index 0000000..41c1546 --- /dev/null +++ b/simulation/test_ip_filter.cpp @@ -0,0 +1,239 @@ +/* + +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 "test.hpp" +#include "create_torrent.hpp" +#include "settings.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/alert_types.hpp" +#include "simulator/simulator.hpp" +#include "simulator/utils.hpp" +#include "fake_peer.hpp" +#include "utils.hpp" // for print_alerts + +using namespace sim; + + +template +void run_test(Setup const& setup + , HandleAlerts const& on_alert + , Test const& test) +{ + // setup the simulation + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + sim::asio::io_context ios(sim, asio::ip::make_address_v4("50.0.0.1")); + lt::session_proxy zombie; + + // setup settings pack to use for the session (customization point) + lt::settings_pack pack = settings(); + // create session + std::shared_ptr ses = std::make_shared(pack, ios); + + // TODO: 2 ideally this test should also try to connect to the session, + // making sure incoming connections from banned IPs are rejected + + fake_peer p1(sim, "60.0.0.0"); + fake_peer p2(sim, "60.0.0.1"); + fake_peer p3(sim, "60.0.0.2"); + fake_peer p4(sim, "60.0.0.3"); + fake_peer p5(sim, "60.0.0.4"); + std::array test_peers = {{ &p1, &p2, &p3, &p4, &p5 }}; + + // set up test, like adding torrents (customization point) + setup(*ses); + + // the alert notification function is called from within libtorrent's + // context. It's not OK to talk to libtorrent in there, post it back out and + // then ask for alerts. + print_alerts(*ses, [=](lt::session& ses, lt::alert const* a) { + on_alert(ses, a); + }); + + sim::timer t(sim, lt::seconds(60) + , [&](boost::system::error_code const&) + { + test(*ses, test_peers); + + // shut down + zombie = ses->abort(); + + for (auto* p : test_peers) p->close(); + + ses.reset(); + }); + + sim.run(); +} + +void add_ip_filter(lt::session& ses) +{ + lt::ip_filter filter; + // filter out 0-2 inclusive + filter.add_rule( + asio::ip::make_address_v4("60.0.0.0") + , asio::ip::make_address_v4("60.0.0.2") + , lt::ip_filter::blocked); + ses.set_ip_filter(filter); +} + +// set an IP filter, add a torrent, add peers, make sure the correct ones are +// connected to +TORRENT_TEST(apply_ip_filter) +{ + run_test( + [](lt::session& ses) + { + add_ip_filter(ses); + + lt::add_torrent_params params = ::create_torrent(0, false); + params.flags &= ~lt::torrent_flags::auto_managed; + params.flags &= ~lt::torrent_flags::paused; + ses.async_add_torrent(params); + }, + + [&](lt::session&, lt::alert const* a) + { + if (auto at = lt::alert_cast(a)) + { + lt::torrent_handle h = at->handle; + add_fake_peers(h); + } + }, + + [](lt::session&, std::array& test_peers) + { + check_accepted(test_peers, {{false, false, false, true, true}} ); + } + ); +} + +// add a torrent, set an IP filter, add peers, make sure the correct ones are +// connected to +TORRENT_TEST(update_ip_filter) +{ + run_test( + [](lt::session& ses) + { + lt::add_torrent_params params = ::create_torrent(0, false); + params.flags &= ~lt::torrent_flags::auto_managed; + params.flags &= ~lt::torrent_flags::paused; + ses.async_add_torrent(params); + }, + + [&](lt::session& ses, lt::alert const* a) + { + if (auto at = lt::alert_cast(a)) + { + // here we add the IP filter after the torrent has already been + // added + add_ip_filter(ses); + + lt::torrent_handle h = at->handle; + add_fake_peers(h); + } + }, + + [](lt::session&, std::array& test_peers) + { + check_accepted(test_peers, {{false, false, false, true, true}} ); + } + ); +} + +TORRENT_TEST(apply_ip_filter_to_torrent) +{ + run_test( + [](lt::session& ses) + { + add_ip_filter(ses); + + lt::add_torrent_params params = ::create_torrent(0, false); + params.flags &= ~lt::torrent_flags::auto_managed; + params.flags &= ~lt::torrent_flags::paused; + + // disable the IP filter! + params.flags &= ~lt::torrent_flags::apply_ip_filter; + ses.async_add_torrent(params); + }, + + [&](lt::session&, lt::alert const* a) + { + if (auto at = lt::alert_cast(a)) + { + lt::torrent_handle h = at->handle; + add_fake_peers(h); + } + }, + + [](lt::session&, std::array& test_peers) + { + // since the IP filter didn't apply to this torrent, it should have hit + // all peers + check_accepted(test_peers, {{true, true, true, true, true}} ); + } + ); +} + +// make sure IP filters apply to trackers +TORRENT_TEST(ip_filter_trackers) +{ + run_test( + [](lt::session& ses) + { + add_ip_filter(ses); + + lt::add_torrent_params params = ::create_torrent(0, false); + params.flags &= ~lt::torrent_flags::auto_managed; + params.flags &= ~lt::torrent_flags::paused; + params.trackers = { + "http://60.0.0.0:6881/announce" + , "http://60.0.0.1:6881/announce" + , "http://60.0.0.2:6881/announce" + , "http://60.0.0.3:6881/announce" + , "http://60.0.0.4:6881/announce" + }; + ses.async_add_torrent(params); + }, + + [](lt::session&, lt::alert const*) {}, + [](lt::session&, std::array& test_peers) + { + check_accepted(test_peers, {{false, false, false, true, true}} ); + } + ); +} + diff --git a/simulation/test_metadata_extension.cpp b/simulation/test_metadata_extension.cpp new file mode 100644 index 0000000..04a7323 --- /dev/null +++ b/simulation/test_metadata_extension.cpp @@ -0,0 +1,250 @@ +/* + +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 "test_utils.hpp" +#include "settings.hpp" +#include "setup_swarm.hpp" +#include "utils.hpp" + +#include "libtorrent/session.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/extensions/ut_metadata.hpp" + +using namespace lt; + +#ifndef TORRENT_DISABLE_EXTENSIONS + +enum flags_t +{ + // disconnect immediately after receiving the metadata (to test that + // edge case, it caused a crash once) + disconnect = 1, + + // force encryption (to make sure the plugin uses the peer_connection + // API in a compatible way) + full_encryption = 2, + + // have the downloader connect to the seeder + // (instead of the other way around) + reverse = 4, + + // only use uTP + utp = 8, + + // upload-only mode + upload_only = 16, + + // re-add the torrent after removing + readd = 32, + + // token limit is too low + token_limit = 64, +}; + +void run_metadata_test(int flags) +{ + int metadata_alerts = 0; + int metadata_failed_alerts = 0; + + sim::default_config cfg; + sim::simulation sim{cfg}; + + lt::settings_pack default_settings = settings(); + + if (flags & full_encryption) + enable_enc(default_settings); + + if (flags & utp) + utp_only(default_settings); + + if (flags & token_limit) + default_settings.set_int(settings_pack::metadata_token_limit, 10); + + + lt::add_torrent_params default_add_torrent; + if (flags & upload_only) + { + default_add_torrent.flags |= torrent_flags::upload_mode; + } + + std::shared_ptr ti; + + // TODO: we use real_disk here because the test disk io doesn't support + // multiple torrents, and readd will add back the same torrent before the + // first one is done being removed + setup_swarm(2, ((flags & reverse) ? swarm_test::upload : swarm_test::download) + | ((flags & readd) ? swarm_test::real_disk : swarm_test_t{}) + , sim + , default_settings + , default_add_torrent + // add session + , [](lt::settings_pack&) {} + // add torrent + , [&ti](lt::add_torrent_params& params) { + // we want to add the torrent via magnet link + error_code ec; + ti = params.ti; + params.ti.reset(); + add_torrent_params const p = parse_magnet_uri( + lt::make_magnet_uri(*ti), ec); + TEST_CHECK(!ec); + params.name = p.name; + params.trackers = p.trackers; + params.tracker_tiers = p.tracker_tiers; + params.url_seeds = p.url_seeds; + params.info_hashes = p.info_hashes; + params.peers = p.peers; +#ifndef TORRENT_DISABLE_DHT + params.dht_nodes = p.dht_nodes; +#endif + params.flags &= ~torrent_flags::upload_mode; + } + // on alert + , [&](lt::alert const* a, lt::session& ses) { + + if (alert_cast(a)) + { + metadata_failed_alerts += 1; + } + else if (alert_cast(a)) + { + metadata_alerts += 1; + + if (flags & disconnect) + { + ses.remove_torrent(ses.get_torrents()[0]); + } + + if (flags & readd) + { + add_torrent_params p = default_add_torrent; + p.ti = ti; + p.save_path = "."; + ses.add_torrent(p); + } + } + } + // terminate + , [&](int ticks, lt::session& ses) -> bool + { + if (flags & reverse) + { + return true; + } + + if (ticks > 70) + { + TEST_ERROR("timeout"); + return true; + } + if ((flags & token_limit) && metadata_failed_alerts > 0) + { + return true; + } + if ((flags & disconnect) && metadata_alerts > 0) + { + return true; + } + if ((flags & upload_only) && has_metadata(ses)) + { + // the other peer is in upload mode and should not have sent any + // actual payload to us + TEST_CHECK(!is_seed(ses)); + return true; + } + + if (is_seed(ses)) + { + TEST_CHECK((flags & upload_only) == 0); + return true; + } + + return false; + }); + + if (flags & token_limit) + { + TEST_EQUAL(metadata_failed_alerts, 1); + } + else + { + TEST_EQUAL(metadata_alerts, 1); + } +} + +TORRENT_TEST(ut_metadata_encryption_reverse) +{ + run_metadata_test(full_encryption | reverse); +} + +TORRENT_TEST(ut_metadata_encryption_utp) +{ + run_metadata_test(full_encryption | utp); +} + +TORRENT_TEST(ut_metadata_reverse) +{ + run_metadata_test(reverse); +} + +TORRENT_TEST(ut_metadata_upload_only) +{ + run_metadata_test(upload_only); +} + +TORRENT_TEST(ut_metadata_disconnect) +{ + run_metadata_test(disconnect); +} + +TORRENT_TEST(ut_metadata_disconnect_readd) +{ + run_metadata_test(disconnect | readd); +} + +TORRENT_TEST(ut_metadata_upload_only_disconnect_readd) +{ + run_metadata_test(upload_only | disconnect | readd); +} + +TORRENT_TEST(ut_metadata_token_limit) +{ + run_metadata_test(token_limit); +} + +#else +TORRENT_TEST(disabled) {} +#endif // TORRENT_DISABLE_EXTENSIONS diff --git a/simulation/test_optimistic_unchoke.cpp b/simulation/test_optimistic_unchoke.cpp new file mode 100644 index 0000000..daf1e6e --- /dev/null +++ b/simulation/test_optimistic_unchoke.cpp @@ -0,0 +1,173 @@ +/* + +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 "setup_swarm.hpp" +#include "test.hpp" +#include "create_torrent.hpp" +#include "bittorrent_peer.hpp" +#include "settings.hpp" +#include "utils.hpp" +#include "simulator/utils.hpp" +#include "setup_transfer.hpp" // for addr() + +#include "libtorrent/alert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/deadline_timer.hpp" + +#include + +using namespace lt; + +struct choke_state +{ + choke_state() : unchoke_duration(lt::seconds(0)), choked(true) {} + lt::time_duration unchoke_duration; + lt::time_point last_unchoke; + bool choked; +}; + +TORRENT_TEST(optimistic_unchoke) +{ + int const num_nodes = 20; + lt::time_duration const test_duration + = lt::seconds(num_nodes * 90); + + dsl_config network_cfg; + sim::simulation sim{network_cfg}; + + io_context ios(sim, addr("50.1.0.0")); + lt::time_point start_time(lt::clock_type::now()); + + lt::add_torrent_params atp = ::create_torrent(0); + atp.flags &= ~torrent_flags::auto_managed; + atp.flags &= ~torrent_flags::paused; + + lt::settings_pack pack = settings(); + // only allow an optimistic unchoke slot + pack.set_int(settings_pack::unchoke_slots_limit, 1); + pack.set_int(settings_pack::num_optimistic_unchoke_slots, 1); + pack.set_int(settings_pack::peer_timeout, 9999); + + std::vector peer_choke_state(num_nodes); + + session_proxy proxy; + + auto ses = std::make_shared(pack, ios); + ses->async_add_torrent(atp); + + std::vector> io_context; + std::vector> peers; + + print_alerts(*ses); + + sim::timer t(sim, lt::seconds(0), [&](boost::system::error_code const&) + { + for (int i = 0; i < num_nodes; ++i) + { + // create a new io_context + char ep[30]; + std::snprintf(ep, sizeof(ep), "50.0.%d.%d", (i + 1) >> 8, (i + 1) & 0xff); + io_context.push_back(std::make_shared( + sim, addr(ep))); + peers.push_back(std::make_shared(*io_context.back() + , [&,i](int msg, char const* /* buf */, int /* len */) + { + choke_state& cs = peer_choke_state[i]; + if (msg == 0) + { + // choke + if (!cs.choked) + { + cs.choked = true; + cs.unchoke_duration += lt::clock_type::now() - cs.last_unchoke; + } + } + else if (msg == 1) + { + // unchoke + if (cs.choked) + { + cs.choked = false; + cs.last_unchoke = lt::clock_type::now(); + } + } + else + { + return; + } + + char const* msg_str[] = {"choke", "unchoke"}; + + lt::time_duration d = lt::clock_type::now() - start_time; + std::uint32_t const millis = std::uint32_t( + lt::duration_cast(d).count()); + printf("\x1b[35m%4d.%03d: [%d] %s (%d ms)\x1b[0m\n" + , millis / 1000, millis % 1000, i, msg_str[msg] + , int(lt::duration_cast(cs.unchoke_duration).count())); + } + , *atp.ti + , tcp::endpoint(addr("50.1.0.0"), 6881) + , peer_conn::peer_mode_t::idle)); + } + }); + + sim::timer t2(sim, test_duration, [&](boost::system::error_code const&) + { + for (auto& p : peers) + { + p->abort(); + } + proxy = ses->abort(); + ses.reset(); + }); + + sim.run(); + + std::int64_t const duration_ms = lt::duration_cast(test_duration).count(); + std::int64_t const average_unchoke_time = duration_ms / num_nodes; + printf("EXPECT: %" PRId64 " ms\n", average_unchoke_time); + for (auto& cs : peer_choke_state) + { + if (!cs.choked) + { + cs.choked = true; + cs.unchoke_duration += lt::clock_type::now() - cs.last_unchoke; + } + std::int64_t const unchoke_duration = lt::duration_cast(cs.unchoke_duration).count(); + printf("%" PRId64 " ms\n", unchoke_duration); + TEST_CHECK(std::abs(unchoke_duration - average_unchoke_time) < 1500); + } +} diff --git a/simulation/test_pause.cpp b/simulation/test_pause.cpp new file mode 100644 index 0000000..36bacf8 --- /dev/null +++ b/simulation/test_pause.cpp @@ -0,0 +1,335 @@ +/* + +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/session.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "settings.hpp" +#include "fake_peer.hpp" +#include "utils.hpp" +#include "create_torrent.hpp" +#include "simulator/simulator.hpp" +#include "simulator/utils.hpp" +#include + +using namespace sim; +using namespace lt; + +using sim::asio::ip::address_v4; + +// this is the general template for these tests. create the session with custom +// settings (Settings), set up the test, by adding torrents with certain +// arguments (Setup), run the test and verify the end state (Test) +template +void run_test(Setup const& setup, Torrent const& torrent + , Test const& test, Check const& check) +{ + // setup the simulation + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + std::unique_ptr ios = make_io_context(sim, 0); + lt::session_proxy zombie; + + // setup settings pack to use for the session (customization point) + lt::settings_pack pack = settings(); + + // create session + std::shared_ptr ses = std::make_shared(pack, *ios); + + setup(*ses); + + fake_peer p1(sim, "60.0.0.0"); + fake_peer p2(sim, "60.0.0.1"); + fake_peer p3(sim, "60.0.0.2"); + std::array test_peers = {{ &p1, &p2, &p3 }}; + + // add torrent + lt::add_torrent_params params = ::create_torrent(0, false); + params.flags &= ~lt::torrent_flags::auto_managed; + params.flags &= ~lt::torrent_flags::paused; + ses->async_add_torrent(std::move(params)); + + lt::torrent_handle h; + print_alerts(*ses, [&](lt::session& ses, lt::alert const* a) { + auto at = lt::alert_cast(a); + if (at == nullptr) return; + h = at->handle; + + // disable the print_alert object from polling any more alerts + ses.set_alert_notify([]{}); + + torrent(ses, h, test_peers); + }); + + sim::timer t1(sim, lt::seconds(5) + , [&](boost::system::error_code const&) + { + test(*ses, h, test_peers); + }); + + // set up a timer to fire later, to verify everything we expected to happen + // happened + sim::timer t2(sim, lt::seconds(10) + , [&](boost::system::error_code const&) + { + check(*ses, h, test_peers); + + // shut down + zombie = ses->abort(); + ses.reset(); + }); + + sim.run(); +} + +// make sure the torrent disconnects all its peers when it's paused +TORRENT_TEST(torrent_paused_disconnect) +{ + run_test( + [](lt::session&) {}, + [](lt::session&, lt::torrent_handle h, std::array&) { + add_fake_peers(h, 3); + }, + + [](lt::session&, lt::torrent_handle h, std::array& test_peers) { + check_accepted(test_peers, {{true, true, true}}); + check_connected(test_peers, {{true, true, true}}); + check_disconnected(test_peers, {{false, false, false}}); + h.pause(); + }, + + [](lt::session&, lt::torrent_handle h, std::array& test_peers) { + check_disconnected(test_peers, {{true, true, true}}); + TEST_CHECK(h.status().flags & torrent_flags::paused); + }); +} + +// make sure the torrent disconnects all its peers when the session is paused +TORRENT_TEST(session_paused_disconnect) +{ + run_test( + [](lt::session&) {}, + [](lt::session&, lt::torrent_handle h, std::array&) { + add_fake_peers(h, 3); + }, + + [](lt::session& ses, lt::torrent_handle h, std::array& test_peers) { + check_accepted(test_peers, {{true, true, true}}); + check_connected(test_peers, {{true, true, true}}); + check_disconnected(test_peers, {{false, false, false}}); + ses.pause(); + }, + + [](lt::session&, lt::torrent_handle h, std::array& test_peers) { + check_disconnected(test_peers, {{true, true, true}}); + + // the torrent isn't paused, the session is + TEST_CHECK(!(h.status().flags & torrent_flags::paused)); + }); +} + +// make sure a torrent is not connecting to any peers when added to a paused +// session +TORRENT_TEST(paused_session_add_torrent) +{ + run_test( + [](lt::session& ses) { ses.pause(); }, + [](lt::session&, lt::torrent_handle h, std::array&) { + add_fake_peers(h, 3); + }, + + [](lt::session&, lt::torrent_handle, std::array& test_peers) { + check_accepted(test_peers, {{false, false, false}}); + }, + + [](lt::session&, lt::torrent_handle h, std::array&) { + // the torrent isn't paused, the session is + TEST_CHECK(!(h.status().flags & torrent_flags::paused)); + }); +} + +// make sure the torrent isn't connecting to peers when it's paused +TORRENT_TEST(paused_torrent_add_peers) +{ + run_test( + [](lt::session&) {}, + [](lt::session&, lt::torrent_handle h, std::array&) { + h.pause(); + + add_fake_peers(h, 3); + }, + + [](lt::session&, lt::torrent_handle, std::array& test_peers) { + check_accepted(test_peers, {{false, false, false}}); + }, + + [](lt::session&, lt::torrent_handle h, std::array&) { + TEST_CHECK(h.status().flags & torrent_flags::paused); + }); +} + +// make sure we post the torrent_paused alert when pausing a torrent +TORRENT_TEST(torrent_paused_alert) +{ + run_test( + [](lt::session&) {}, + [](lt::session&, lt::torrent_handle, std::array&) {}, + + [](lt::session&, lt::torrent_handle h, std::array&) { + TEST_CHECK(!(h.status().flags & torrent_flags::paused)); + h.pause(); + }, + + [](lt::session& ses, lt::torrent_handle h, std::array&) { + TEST_CHECK(h.status().flags & torrent_flags::paused); + + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + int num_resume = 0; + int num_paused = 0; + for (alert* a : alerts) + { + std::printf("%-3d %s\n", int(duration_cast(a->timestamp() + - start_time).count()), a->message().c_str()); + if (lt::alert_cast(a)) ++num_resume; + if (lt::alert_cast(a)) ++num_paused; + } + + TEST_EQUAL(num_resume, 0); + TEST_EQUAL(num_paused, 1); + }); +} + +// make sure we post the torrent_paused alert when pausing the session +TORRENT_TEST(session_paused_alert) +{ + run_test( + [](lt::session&) {}, + [](lt::session&, lt::torrent_handle, std::array&) {}, + + [](lt::session& ses, lt::torrent_handle h, std::array&) { + TEST_CHECK(!(h.status().flags & torrent_flags::paused)); + ses.pause(); + }, + + [](lt::session& ses, lt::torrent_handle h, std::array&) { + TEST_CHECK(!(h.status().flags & torrent_flags::paused)); + + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + int num_resume = 0; + int num_paused = 0; + for (alert* a : alerts) + { + std::printf("%-3d %s\n", int(duration_cast(a->timestamp() + - start_time).count()), a->message().c_str()); + if (lt::alert_cast(a)) ++num_resume; + if (lt::alert_cast(a)) ++num_paused; + } + + TEST_EQUAL(num_resume, 0); + TEST_EQUAL(num_paused, 1); + }); +} + +// make sure we post both the paused and resumed alert when pausing and resuming +// the session. +TORRENT_TEST(session_pause_resume) +{ + run_test( + [](lt::session&) {}, + [](lt::session& ses, lt::torrent_handle h, std::array&) { + TEST_CHECK(!(h.status().flags & torrent_flags::paused)); + ses.pause(); + }, + + [](lt::session& ses, lt::torrent_handle h, std::array&) { + TEST_CHECK(!(h.status().flags & torrent_flags::paused)); + ses.resume(); + }, + + [](lt::session& ses, lt::torrent_handle h, std::array&) { + TEST_CHECK(!(h.status().flags & torrent_flags::paused)); + + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + int num_resume = 0; + int num_paused = 0; + for (alert* a : alerts) + { + std::printf("%-3d %s\n", int(duration_cast(a->timestamp() + - start_time).count()), a->message().c_str()); + if (lt::alert_cast(a)) ++num_resume; + if (lt::alert_cast(a)) ++num_paused; + } + + TEST_EQUAL(num_resume, 1); + TEST_EQUAL(num_paused, 1); + }); +} + +// make sure peers added to a (non-paused) torrent in a paused session are +// connected once the session is resumed +TORRENT_TEST(session_pause_resume_connect) +{ + run_test( + [](lt::session&) {}, + [](lt::session& ses, lt::torrent_handle h, std::array&) { + TEST_CHECK(!(h.status().flags & torrent_flags::paused)); + ses.pause(); + add_fake_peers(h, 3); + }, + + [](lt::session& ses, lt::torrent_handle h, std::array& test_peers) { + TEST_CHECK(!(h.status().flags & torrent_flags::paused)); + check_accepted(test_peers, {{false, false, false}}); + ses.resume(); + }, + + [](lt::session&, lt::torrent_handle h, std::array& test_peers) { + TEST_CHECK(!(h.status().flags & torrent_flags::paused)); + + check_accepted(test_peers, {{true, true, true}}); + }); +} + diff --git a/simulation/test_pe_crypto.cpp b/simulation/test_pe_crypto.cpp new file mode 100644 index 0000000..9e316b9 --- /dev/null +++ b/simulation/test_pe_crypto.cpp @@ -0,0 +1,197 @@ +/* + +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/pe_crypto.hpp" +#include "libtorrent/session.hpp" + +#include "setup_transfer.hpp" +#include "test.hpp" +#include "settings.hpp" +#include "setup_swarm.hpp" +#include "utils.hpp" + +#if !defined TORRENT_DISABLE_ENCRYPTION + +using namespace lt; + +char const* pe_policy(int const policy) +{ + if (policy == settings_pack::pe_disabled) return "disabled"; + else if (policy == settings_pack::pe_enabled) return "enabled"; + else if (policy == settings_pack::pe_forced) return "forced"; + return "unknown"; +} + +void display_pe_settings(lt::settings_pack const& s) +{ + std::printf("out_enc_policy - %s\tin_enc_policy - %s\n" + , pe_policy(s.get_int(settings_pack::out_enc_policy)) + , pe_policy(s.get_int(settings_pack::in_enc_policy))); + + std::printf("enc_level - %s\t\tprefer_rc4 - %s\n" + , s.get_int(settings_pack::allowed_enc_level) == settings_pack::pe_plaintext ? "plaintext" + : s.get_int(settings_pack::allowed_enc_level) == settings_pack::pe_rc4 ? "rc4" + : s.get_int(settings_pack::allowed_enc_level) == settings_pack::pe_both ? "both" : "unknown" + , s.get_bool(settings_pack::prefer_rc4) ? "true": "false"); +} + +void test_transfer(int enc_policy, int level, bool prefer_rc4) +{ + lt::settings_pack default_settings = settings(); + default_settings.set_bool(settings_pack::prefer_rc4, prefer_rc4); + default_settings.set_int(settings_pack::in_enc_policy, enc_policy); + default_settings.set_int(settings_pack::out_enc_policy, enc_policy); + default_settings.set_int(settings_pack::allowed_enc_level, level); + display_pe_settings(default_settings); + + sim::default_config cfg; + sim::simulation sim{cfg}; + + lt::add_torrent_params default_add_torrent; + default_add_torrent.flags &= ~lt::torrent_flags::paused; + default_add_torrent.flags &= ~lt::torrent_flags::auto_managed; + setup_swarm(2, swarm_test::download, sim, default_settings, default_add_torrent + // add session + , [](lt::settings_pack& pack) { + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_enabled); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_enabled); + pack.set_int(settings_pack::allowed_enc_level, settings_pack::pe_both); + pack.set_bool(settings_pack::prefer_rc4, false); + } + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [](lt::alert const*, lt::session&) {} + // terminate + , [](int ticks, lt::session& ses) -> bool + { + if (ticks > 20) + { + TEST_ERROR("timeout"); + return true; + } + return is_seed(ses); + }); +} + +TORRENT_TEST(pe_disabled) +{ + test_transfer(settings_pack::pe_disabled, settings_pack::pe_plaintext, false); +} + +TORRENT_TEST(forced_plaintext) +{ + test_transfer(settings_pack::pe_forced, settings_pack::pe_plaintext, false); +} + +TORRENT_TEST(forced_rc4) +{ + test_transfer(settings_pack::pe_forced, settings_pack::pe_rc4, true); +} + +TORRENT_TEST(forced_both) +{ + test_transfer(settings_pack::pe_forced, settings_pack::pe_both, false); +} + +TORRENT_TEST(forced_both_prefer_rc4) +{ + test_transfer(settings_pack::pe_forced, settings_pack::pe_both, true); +} + +TORRENT_TEST(enabled_plaintext) +{ + test_transfer(settings_pack::pe_forced, settings_pack::pe_plaintext, false); +} + +TORRENT_TEST(enabled_rc4) +{ + test_transfer(settings_pack::pe_enabled, settings_pack::pe_rc4, false); +} + +TORRENT_TEST(enabled_both) +{ + test_transfer(settings_pack::pe_enabled, settings_pack::pe_both, false); +} + +TORRENT_TEST(enabled_both_prefer_rc4) +{ + test_transfer(settings_pack::pe_enabled, settings_pack::pe_both, true); +} + +// make sure that a peer with encryption disabled cannot talk to a peer with +// encryption forced +TORRENT_TEST(disabled_failing) +{ + lt::settings_pack default_settings = settings(); + default_settings.set_bool(settings_pack::prefer_rc4, false); + default_settings.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); + default_settings.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + default_settings.set_int(settings_pack::allowed_enc_level, settings_pack::pe_both); + display_pe_settings(default_settings); + + sim::default_config cfg; + sim::simulation sim{cfg}; + + lt::add_torrent_params default_add_torrent; + default_add_torrent.flags &= ~lt::torrent_flags::paused; + default_add_torrent.flags &= ~lt::torrent_flags::auto_managed; + setup_swarm(2, swarm_test::download, sim, default_settings, default_add_torrent + // add session + , [](lt::settings_pack& pack) { + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_forced); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_forced); + pack.set_int(settings_pack::allowed_enc_level, settings_pack::pe_both); + pack.set_bool(settings_pack::prefer_rc4, true); + } + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [](lt::alert const*, lt::session&) {} + // terminate + , [](int ticks, lt::session& ses) -> bool + { + // this download should never succeed + TEST_CHECK(!is_seed(ses)); + return ticks > 120; + }); +} +#else +TORRENT_TEST(disabled) +{ + std::printf("PE test not run because it's disabled\n"); +} +#endif + diff --git a/simulation/test_peer_connection.cpp b/simulation/test_peer_connection.cpp new file mode 100644 index 0000000..768aba8 --- /dev/null +++ b/simulation/test_peer_connection.cpp @@ -0,0 +1,309 @@ +/* + +Copyright (c) 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/torrent_handle.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/disabled_disk_io.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/torrent_flags.hpp" +#include "settings.hpp" +#include "fake_peer.hpp" +#include "utils.hpp" +#include "test_utils.hpp" +#include "setup_transfer.hpp" +#include "create_torrent.hpp" +#include "simulator/simulator.hpp" +#include "simulator/utils.hpp" +#include "simulator/queue.hpp" + +template +void test_peer(lt::torrent_flags_t const flags + , PeerFun&& peer_fun + , TestFun&& test) +{ + sim::default_config cfg; + sim::simulation sim{cfg}; + auto ios = std::make_unique(sim, lt::make_address_v4("50.0.0.1")); + lt::session_proxy zombie; + + lt::session_params sp; + sp.settings = settings(); + sp.settings.set_int(lt::settings_pack::alert_mask, lt::alert_category::all & ~lt::alert_category::stats); + if (!(flags & lt::torrent_flags::seed_mode)) + sp.disk_io_constructor = lt::disabled_disk_io_constructor; + + // create session + std::shared_ptr ses = std::make_shared(sp, *ios); + + auto peer = std::make_unique(sim, "60.0.0.1"); + + // add torrent + lt::add_torrent_params params + = (flags & lt::torrent_flags::seed_mode) + ? ::create_torrent(0, true) : ::create_torrent(0, false); + int const num_pieces = params.ti->num_pieces(); + params.flags &= ~lt::torrent_flags::auto_managed; + params.flags &= ~lt::torrent_flags::paused; + params.flags |= flags; + lt::sha1_hash const info_hash = params.ti->info_hash(); + ses->async_add_torrent(std::move(params)); + + lt::torrent_handle h; + bool connected = false; + print_alerts(*ses, [&](lt::session& ses, lt::alert const* a) { + if (auto* at = lt::alert_cast(a)) + { + h = at->handle; + + TORRENT_ASSERT(!connected); + peer->connect_to(ep("50.0.0.1", 6881), info_hash); + peer_fun(*peer.get(), num_pieces); + connected = true; + } + if (connected) + test(a); + }); + + // set up a timer to fire later, to shut down + sim::timer t2(sim, lt::seconds(700) + , [&](boost::system::error_code const&) + { + // shut down + zombie = ses->abort(); + ses.reset(); + }); + + sim.run(); +} + +struct peer_errors +{ + void operator()(lt::alert const* a) + { + auto* pe = lt::alert_cast(a); + if (!pe) return; + alerts.push_back(pe->error); + } + + std::vector alerts; +}; + +struct peer_disconnects +{ + void operator()(lt::alert const* a) + { + // when we're expecting an orderly disconnect, make sure we don't also + // get a peer-error. + TEST_CHECK(lt::alert_cast(a) == nullptr); + + auto* pd = lt::alert_cast(a); + if (!pd) return; + alerts.push_back(pd->error); + } + + std::vector alerts; +}; + +struct invalid_requests +{ + void operator()(lt::alert const* a) + { + // we don't expect a peer error + TEST_CHECK(lt::alert_cast(a) == nullptr); + + auto* ir = lt::alert_cast(a); + if (!ir) return; + alerts.push_back(ir->request); + } + + std::vector alerts; +}; + +using vec = std::vector; +using reqs = std::vector; + +TORRENT_TEST(alternate_have_all_have_none) +{ + peer_disconnects d; + test_peer({}, [](fake_peer& p, int) + { + p.send_have_all(); + p.send_have_none(); + p.send_have_all(); + p.send_have_none(); + } + , d); + TEST_CHECK(d.alerts == vec{lt::errors::timed_out_inactivity}); +} + +TORRENT_TEST(alternate_have_all_have_none_seed) +{ + peer_disconnects d; + test_peer(lt::torrent_flags::seed_mode, [](fake_peer& p, int) + { + p.send_have_all(); + p.send_have_none(); + p.send_have_all(); + p.send_have_none(); + } + , d); + TEST_CHECK(d.alerts == vec{lt::errors::upload_upload_connection}); +} + +TORRENT_TEST(bitfield_and_have_none) +{ + peer_disconnects d; + test_peer({}, [](fake_peer& p, int const num_pieces) + { + std::vector bitfield(num_pieces, false); + bitfield[lt::random(num_pieces)] = true; + p.send_bitfield(bitfield); + p.send_have_none(); + } + , d); + TEST_CHECK(d.alerts == vec{lt::errors::timed_out_inactivity}); +} + +TORRENT_TEST(bitfield_and_have_all) +{ + peer_disconnects d; + test_peer({}, [](fake_peer& p, int const num_pieces) + { + std::vector bitfield(num_pieces, false); + bitfield[lt::random(num_pieces)] = true; + p.send_bitfield(bitfield); + p.send_have_all(); + } + , d); + TEST_CHECK(d.alerts == vec{lt::errors::timed_out_inactivity}); +} + +TORRENT_TEST(full_bitfield_and_have_all) +{ + peer_disconnects d; + test_peer({}, [](fake_peer& p, int const num_pieces) + { + std::vector bitfield(num_pieces, true); + p.send_bitfield(bitfield); + p.send_have_all(); + } + , d); + TEST_CHECK(d.alerts == vec{lt::errors::timed_out_inactivity}); +} + +TORRENT_TEST(full_bitfield_and_have_none) +{ + peer_disconnects d; + test_peer({}, [](fake_peer& p, int const num_pieces) + { + std::vector bitfield(num_pieces, true); + p.send_bitfield(bitfield); + p.send_have_none(); + } + , d); + TEST_CHECK(d.alerts == vec{lt::errors::timed_out_inactivity}); +} + +TORRENT_TEST(invalid_request) +{ + invalid_requests e; + test_peer({}, [](fake_peer& p, int const num_pieces) + { + p.send_interested(); + p.send_request(1_piece, 0); + } + , e); + TEST_CHECK((e.alerts == reqs{lt::peer_request{1_piece, 0, lt::default_block_size}})); +} + +TORRENT_TEST(large_message) +{ + peer_errors e; + test_peer({}, [](fake_peer& p, int const num_pieces) + { + p.send_large_message(); + } + , e); + TEST_CHECK(e.alerts == vec{lt::errors::packet_too_large}); +} + +TORRENT_TEST(have_all_invalid_msg) +{ + peer_errors e; + test_peer({}, [](fake_peer& p, int const num_pieces) + { + p.send_have_all(); + p.send_invalid_message(); + } + , e); + TEST_CHECK(e.alerts == vec{lt::errors::invalid_message}); +} + +TORRENT_TEST(invalid_message) +{ + peer_errors e; + test_peer({}, [](fake_peer& p, int const num_pieces) + { + p.send_invalid_message(); + } + , e); + TEST_CHECK(e.alerts == vec{lt::errors::invalid_message}); +} + +TORRENT_TEST(short_bitfield) +{ + peer_errors e; + test_peer({}, [](fake_peer& p, int const num_pieces) + { + std::vector bitfield(num_pieces - 1, true); + p.send_bitfield(bitfield); + } + , e); + TEST_CHECK(e.alerts == vec{lt::errors::invalid_bitfield_size}); +} + +TORRENT_TEST(long_bitfield) +{ + peer_errors e; + test_peer({}, [](fake_peer& p, int const num_pieces) + { + std::vector bitfield(num_pieces + 9, true); + p.send_bitfield(bitfield); + } + , e); + TEST_CHECK(e.alerts == vec{lt::errors::invalid_bitfield_size}); +} diff --git a/simulation/test_save_resume.cpp b/simulation/test_save_resume.cpp new file mode 100644 index 0000000..c955a7f --- /dev/null +++ b/simulation/test_save_resume.cpp @@ -0,0 +1,88 @@ +/* + +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 "setup_swarm.hpp" +#include "test.hpp" +#include "utils.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/write_resume_data.hpp" +#include "settings.hpp" + +using namespace lt; + +TORRENT_TEST(seed_and_suggest_mode) +{ + add_torrent_params resume_data; + + // with seed mode + setup_swarm(2, swarm_test::upload + // add session + , [](lt::settings_pack& pack) { + pack.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); + } + // add torrent + , [](lt::add_torrent_params& params) { + params.flags |= torrent_flags::seed_mode; + } + // on alert + , [&](lt::alert const* a, lt::session&) + { + auto* sr = alert_cast(a); + if (sr == nullptr) return; + + resume_data = sr->params; + } + // terminate + , [](int ticks, lt::session& ses) -> bool + { + if (ticks < 5) return false; + if (ticks == 5) + { + ses.get_torrents()[0].save_resume_data(); + return false; + } + return true; + }); + + printf("save-resume: %s\n", write_resume_data(resume_data).to_string().c_str()); + TEST_CHECK(resume_data.flags & torrent_flags::seed_mode); + + // there should not be any pieces in a seed-mode torrent + auto const pieces = resume_data.have_pieces; + for (bool const c : pieces) + { + TEST_CHECK(c); + } +} + + diff --git a/simulation/test_session.cpp b/simulation/test_session.cpp new file mode 100644 index 0000000..4ddcb65 --- /dev/null +++ b/simulation/test_session.cpp @@ -0,0 +1,254 @@ +/* + +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 "setup_swarm.hpp" +#include "test.hpp" +#include "utils.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/extensions.hpp" +#include "simulator/simulator.hpp" +#include "simulator/utils.hpp" // for timer +#include "settings.hpp" +#include "create_torrent.hpp" +#include "setup_transfer.hpp" // for addr() + +using namespace lt; + +TORRENT_TEST(seed_mode) +{ + // with seed mode + setup_swarm(2, swarm_test::upload + // add session + , [](lt::settings_pack& pack) { + // make sure the session works with a tick interval of 5 seconds + pack.set_int(settings_pack::tick_interval, 5000); + } + // add torrent + , [](lt::add_torrent_params& params) { + params.flags |= torrent_flags::seed_mode; + } + // on alert + , [](lt::alert const*, lt::session&) {} + // terminate + , [](int ticks, lt::session&) -> bool { + // we don't need to finish seeding, exit after 20 seconds + return ticks > 20; + }); +} + +#ifndef TORRENT_DISABLE_LOGGING +TORRENT_TEST(ip_notifier_setting) +{ + int s_tick = 0; + int working_count = 0; + + setup_swarm(1, swarm_test::upload + // add session + , [](lt::settings_pack& pack) + { + pack.set_int(settings_pack::tick_interval, 1000); + pack.set_int(settings_pack::alert_mask, alert_category::all); + } + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [&s_tick, &working_count](lt::alert const* a, lt::session&) + { + std::string const msg = a->message(); + if (msg.find("received error on_ip_change:") != std::string::npos) + { + TEST_CHECK(s_tick == 0 || s_tick == 2); + working_count++; + } + } + // terminate + , [&s_tick](int ticks, lt::session& ses) -> bool { + + if (ticks == 1) + { + settings_pack sp; + sp.set_bool(settings_pack::enable_ip_notifier, false); + ses.apply_settings(sp); + } + else if (ticks == 2) + { + settings_pack sp; + sp.set_bool(settings_pack::enable_ip_notifier, true); + ses.apply_settings(sp); + } + + s_tick = ticks; + + // exit after 3 seconds + return ticks > 3; + }); + + TEST_EQUAL(working_count, 2); +} +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS +struct test_plugin : lt::torrent_plugin +{ + bool m_new_connection = false; + bool m_files_checked = false; + + std::shared_ptr new_connection(peer_connection_handle const&) override + { + m_new_connection = true; + return std::shared_ptr(); + } + + void on_files_checked() override + { + m_files_checked = true; + } +}; + +TORRENT_TEST(add_extension_while_transfer) +{ + bool done = false; + auto p = std::make_shared(); + + setup_swarm(2, swarm_test::download + // add session + , [](lt::settings_pack& pack) + { + pack.set_int(settings_pack::tick_interval, 1000); + pack.set_int(settings_pack::alert_mask, alert_category::all); + } + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [&done, p](lt::alert const* a, lt::session&) + { + if (a->type() == peer_connect_alert::alert_type) + { + auto create_test_plugin = [p](torrent_handle const&, client_data_t) + { return p; }; + + lt::torrent_handle th = alert_cast(a)->handle; + th.add_extension(create_test_plugin); + + done = true; + } + } + // terminate + , [&done](int ticks, lt::session&) -> bool + { + // exit after 10 seconds + return ticks > 10 || done; + }); + + TEST_CHECK(done); + TEST_CHECK(p->m_new_connection); + TEST_CHECK(p->m_files_checked); +} +#endif // TORRENT_DISABLE_EXTENSIONS + +// make sure TCP and UDP listen sockets use the same port +TORRENT_TEST(tie_listen_ports) +{ + using namespace libtorrent; + + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + sim::asio::io_context ios{ sim, addr("50.0.0.1")}; + + lt::session_proxy zombie; + + // create session + auto pack = settings(); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:0"); + pack.set_int(settings_pack::alert_mask, alert_category::error + | alert_category::status + | alert_category::torrent_log); + + auto ses = std::make_shared(pack, ios); + + std::vector listen_ports; + + // only monitor alerts for session 0 (the downloader) + print_alerts(*ses, [&](lt::session&, lt::alert const* a){ + if (auto const* la = alert_cast(a)) + { + listen_ports.push_back(la->port); + } + }); + + sim::timer t(sim, lt::seconds(30), [&](boost::system::error_code const&) + { + // TEST + zombie = ses->abort(); + ses.reset(); + + TEST_CHECK(listen_ports.size() > 0); + int const port = listen_ports.front(); + for (int const p : listen_ports) + TEST_EQUAL(p, port); + }); + + sim.run(); +} + +// make sure passing in the session::paused flag does indeed start the session +// paused +TORRENT_TEST(construct_paused_session) +{ + using namespace libtorrent; + + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + sim::asio::io_context ios { sim, addr("50.0.0.1")}; + + lt::session_proxy zombie; + + // create session + auto pack = settings(); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:0"); + pack.set_int(settings_pack::alert_mask, alert_category::error + | alert_category::status + | alert_category::torrent_log); + + auto ses = std::make_shared(pack, ios, session::paused); + + sim::timer t(sim, lt::seconds(30), [&](boost::system::error_code const&) + { + TEST_CHECK(ses->is_paused()); + zombie = ses->abort(); + ses.reset(); + }); + + sim.run(); +} diff --git a/simulation/test_socks5.cpp b/simulation/test_socks5.cpp new file mode 100644 index 0000000..1b4854f --- /dev/null +++ b/simulation/test_socks5.cpp @@ -0,0 +1,283 @@ +/* + +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/session.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "simulator/http_server.hpp" +#include "settings.hpp" +#include "create_torrent.hpp" +#include "setup_transfer.hpp" // for addr() +#include "simulator/simulator.hpp" +#include "setup_swarm.hpp" +#include "utils.hpp" +#include "simulator/socks_server.hpp" +#include "simulator/utils.hpp" +#include "fake_peer.hpp" +#include + +using namespace sim; +using namespace lt; + + +// this is the general template for these tests. create the session with custom +// settings (Settings), set up the test, by adding torrents with certain +// arguments (Setup), run the test and verify the end state (Test) +template +void run_test(Setup const& setup + , HandleAlerts const& on_alert + , Test const& test) +{ + // setup the simulation + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + std::unique_ptr ios = make_io_context(sim, 0); + lt::session_proxy zombie; + + sim::asio::io_context proxy_ios{sim, addr("50.50.50.50") }; + sim::socks_server socks4(proxy_ios, 4444, 4); + sim::socks_server socks5(proxy_ios, 5555, 5); + + lt::settings_pack pack = settings(); + // create session + std::shared_ptr ses = std::make_shared(pack, *ios); + + // set up test, like adding torrents (customization point) + setup(*ses); + + // only monitor alerts for session 0 (the downloader) + print_alerts(*ses, [=](lt::session& ses, lt::alert const* a) { + on_alert(ses, a); + }); + + lt::add_torrent_params params = ::create_torrent(1); + params.flags &= ~lt::torrent_flags::auto_managed; + params.flags &= ~lt::torrent_flags::paused; + params.save_path = save_path(0); + ses->async_add_torrent(params); + + // set up a timer to fire later, to verify everything we expected to happen + // happened + sim::timer t(sim, lt::seconds(100), [&](boost::system::error_code const&) + { + std::printf("shutting down\n"); + // shut down + zombie = ses->abort(); + ses.reset(); + }); + + test(sim, *ses, params.ti); +} + +TORRENT_TEST(socks5_tcp_announce) +{ + using namespace lt; + int tracker_port = -1; + int alert_port = -1; + run_test( + [](lt::session& ses) + { + set_proxy(ses, settings_pack::socks5); + + lt::add_torrent_params params; + params.info_hashes.v1 = sha1_hash("abababababababababab"); + params.trackers.push_back("http://2.2.2.2:8080/announce"); + params.save_path = "."; + ses.async_add_torrent(params); + }, + [&alert_port](lt::session&, lt::alert const* alert) { + if (auto* a = lt::alert_cast(alert)) + { + if (a->socket_type == socket_type_t::utp) + { + alert_port = a->port; + } + } + }, + [&tracker_port](sim::simulation& sim, lt::session& + , std::shared_ptr ti) + { + sim::asio::io_context web_server(sim, make_address_v4("2.2.2.2")); + // listen on port 8080 + sim::http_server http(web_server, 8080); + + http.register_handler("/announce" + , [&tracker_port](std::string method, std::string req + , std::map&) + { + if (req.find("&event=started") != std::string::npos) + { + std::string::size_type port_pos = req.find("&port="); + TEST_CHECK(port_pos != std::string::npos); + if (port_pos != std::string::npos) + { + tracker_port = atoi(req.c_str() + port_pos + 6); + } + } + + return sim::send_response(200, "OK", 27) + "d8:intervali1800e5:peers0:e"; + }); + + sim.run(); + } + ); + + TEST_EQUAL(tracker_port, 1); + TEST_CHECK(alert_port != -1); +} + +TORRENT_TEST(udp_tracker) +{ + using namespace lt; + bool tracker_alert = false; + bool connected = false; + bool announced = false; + run_test( + [](lt::session& ses) + { + set_proxy(ses, settings_pack::socks5); + + // The socks server in libsimulator does not support forwarding UDP + // packets to hostnames (just IPv4 destinations) + settings_pack p; + p.set_bool(settings_pack::proxy_hostnames, false); + ses.apply_settings(p); + + lt::add_torrent_params params; + params.info_hashes.v1 = sha1_hash("abababababababababab"); + params.trackers.push_back("udp://2.2.2.2:8080/announce"); + params.save_path = "."; + ses.async_add_torrent(params); + }, + [&tracker_alert](lt::session&, lt::alert const* alert) { + if (lt::alert_cast(alert)) + tracker_alert = true; + }, + [&](sim::simulation& sim, lt::session& + , std::shared_ptr ti) + { + // listen on port 8080 + udp_server tracker(sim, "2.2.2.2", 8080, + [&](char const* msg, int size) + { + using namespace lt::aux; + std::vector ret; + TEST_CHECK(size >= 16); + + if (size < 16) return ret; + + std::uint64_t connection_id = read_uint64(msg); + std::uint32_t action = read_uint32(msg); + std::uint32_t transaction_id = read_uint32(msg); + + std::uint64_t const conn_id = 0xfeedface1337ull; + + if (action == 0) + { + std::printf("udp connect\n"); + // udp tracker connect + TEST_CHECK(connection_id == 0x41727101980ull); + auto inserter = std::back_inserter(ret); + write_uint32(0, inserter); // connect + write_uint32(transaction_id, inserter); + write_uint64(conn_id, inserter); + connected = true; + } + else if (action == 1) + { + std::printf("udp announce\n"); + // udp tracker announce + TEST_EQUAL(connection_id, conn_id); + + auto inserter = std::back_inserter(ret); + write_uint32(1, inserter); // announce + write_uint32(transaction_id, inserter); + write_uint32(1800, inserter); + write_uint32(0, inserter); // leechers + write_uint32(0, inserter); // seeders + announced = true; + } + else + { + std::printf("unsupported udp tracker action: %d\n", action); + } + return ret; + }); + + sim.run(); + } + ); + + TEST_CHECK(tracker_alert); + TEST_CHECK(connected); + TEST_CHECK(announced); +} + +TORRENT_TEST(socks5_udp_retry) +{ + // this test is asserting that when a UDP associate command fails, we have a + // 5 second delay before we try again. There is no need to actually add a + // torrent for this test, just to open the udp socket with a socks5 proxy + using namespace libtorrent; + + // setup the simulation + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + std::unique_ptr ios = make_io_context(sim, 0); + lt::session_proxy zombie; + + sim::asio::io_context proxy_ios{sim, addr("50.50.50.50") }; + // close UDP associate connectons prematurely + sim::socks_server socks5(proxy_ios, 5555, 5, socks_flag::disconnect_udp_associate); + + lt::settings_pack pack = settings(); + // create session + std::shared_ptr ses = std::make_shared(pack, *ios); + print_alerts(*ses); + set_proxy(*ses, settings_pack::socks5); + + sim::timer t(sim, lt::seconds(60), [&](boost::system::error_code const&) + { + fprintf(stderr, "shutting down\n"); + // shut down + zombie = ses->abort(); + ses.reset(); + }); + sim.run(); + + // number of UDP ASSOCIATE commands invoked on the socks proxy + // We run for 60 seconds. The sokcks5 retry interval is expected to be 5 + // seconds, meaning there should have been 12 connection attempts + TEST_EQUAL(socks5.cmd_counts()[2], 12); +} diff --git a/simulation/test_super_seeding.cpp b/simulation/test_super_seeding.cpp new file mode 100644 index 0000000..b027b7d --- /dev/null +++ b/simulation/test_super_seeding.cpp @@ -0,0 +1,78 @@ +/* + +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 "setup_swarm.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/settings_pack.hpp" +#include "test.hpp" + +using namespace lt; + +#ifndef TORRENT_DISABLE_SUPERSEEDING +TORRENT_TEST(super_seeding) +{ + setup_swarm(5, swarm_test::upload + // add session + , [](lt::settings_pack&) {} + // add torrent + , [](lt::add_torrent_params& params) { + params.flags |= torrent_flags::super_seeding; + } + // on alert + , [](lt::alert const*, lt::session&) {} + // terminate + , [](int, lt::session&) -> bool + { return true; }); +} + +#if TORRENT_ABI_VERSION == 1 +TORRENT_TEST(strict_super_seeding) +{ + setup_swarm(5, swarm_test::upload + // add session + , [](lt::settings_pack& pack) { + pack.set_bool(settings_pack::strict_super_seeding, true); + } + // add torrent + , [](lt::add_torrent_params& params) { + params.flags |= torrent_flags::super_seeding; + } + // on alert + , [](lt::alert const*, lt::session&) {} + // terminate + , [](int, lt::session&) -> bool + { return true; }); +} +#endif +#else +TORRENT_TEST(summy) {} +#endif diff --git a/simulation/test_swarm.cpp b/simulation/test_swarm.cpp new file mode 100644 index 0000000..45a771e --- /dev/null +++ b/simulation/test_swarm.cpp @@ -0,0 +1,1032 @@ +/* + +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 "setup_swarm.hpp" +#include "test.hpp" +#include "utils.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/time.hpp" +#include "settings.hpp" +#include "setup_transfer.hpp" // for ep() +#include "fake_peer.hpp" + +#include "simulator/nat.hpp" +#include "simulator/queue.hpp" +#include "utils.hpp" + +#include + +using namespace lt; + +TORRENT_TEST(seed_mode) +{ + // with seed mode + setup_swarm(3, swarm_test::upload + // add session + , [](lt::settings_pack&) {} + // add torrent + , [](lt::add_torrent_params& params) { + params.flags |= torrent_flags::seed_mode; + } + // on alert + , [](lt::alert const*, lt::session&) {} + // terminate + , [](int, lt::session&) -> bool + { return false; }); +} + +TORRENT_TEST(seed_mode_disable_hash_checks) +{ + // all nodes need to disable hash checking, otherwise the downloader would + // just fail + settings_pack swarm_settings = settings(); + swarm_settings.set_bool(settings_pack::disable_hash_checks, true); + + dsl_config network_cfg; + sim::simulation sim{network_cfg}; + + // with seed mode + setup_swarm(2, swarm_test::upload, sim, swarm_settings, add_torrent_params() + // add session + , [](lt::settings_pack& pack) { + pack.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); + } + // add torrent + , [](lt::add_torrent_params& params) { + params.flags |= torrent_flags::seed_mode; + // just to make sure the disable_hash_checks really work, we + // shouldn't be verifying anything from the storage +// params.storage = disabled_storage_constructor; + } + // on alert + , [](lt::alert const*, lt::session&) {} + // terminate + , [](int, lt::session&) -> bool + { return false; }); +} + +TORRENT_TEST(seed_mode_suggest) +{ + setup_swarm(2, swarm_test::upload + // add session + , [](lt::settings_pack& pack) { + pack.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); +#if TORRENT_ABI_VERSION == 1 + pack.set_int(settings_pack::cache_size, 2); +#endif + } + // add torrent + , [](lt::add_torrent_params& params) { + params.flags |= torrent_flags::seed_mode; + } + // on alert + , [](lt::alert const*, lt::session&) {} + // terminate + , [](int, lt::session&) -> bool + { return true; }); +} + +TORRENT_TEST(plain) +{ + setup_swarm(2, swarm_test::download + // add session + , [](lt::settings_pack&) {} + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [](lt::alert const*, lt::session&) {} + // terminate + , [](int const ticks, lt::session& ses) -> bool + { + if (ticks > 80) + { + TEST_ERROR("timeout"); + return true; + } + if (!is_seed(ses)) return false; + std::printf("completed in %d ticks\n", ticks); + return true; + }); +} + +TORRENT_TEST(session_stats) +{ + std::vector stats = session_stats_metrics(); + int const downloading_idx = find_metric_idx("ses.num_downloading_torrents"); + TEST_CHECK(downloading_idx >= 0); + int const incoming_extended_idx = find_metric_idx("ses.num_incoming_extended"); + TEST_CHECK(incoming_extended_idx >= 0); + + setup_swarm(2, swarm_test::download + // add session + , [](lt::settings_pack&) {} + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [=](lt::alert const* a, lt::session&) + { + auto const* ss = lt::alert_cast(a); + if (!ss) return; + + // there's one downloading torrent + TEST_EQUAL(ss->counters()[downloading_idx], 1); + TEST_EQUAL(ss->counters()[incoming_extended_idx], 1); + } + // terminate + , [](int const ticks, lt::session& ses) -> bool + { + ses.post_session_stats(); + if (ticks > 80) + { + TEST_ERROR("timeout"); + return true; + } + if (!is_seed(ses)) return false; + std::printf("completed in %d ticks\n", ticks); + return true; + }); +} + +// this test relies on picking up log alerts +#ifndef TORRENT_DISABLE_LOGGING +TORRENT_TEST(suggest) +{ + int num_suggests = 0; + setup_swarm(10, swarm_test::upload + // add session + , [](lt::settings_pack& pack) { + pack.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); + pack.set_int(settings_pack::max_suggest_pieces, 10); +#if TORRENT_ABI_VERSION == 1 + pack.set_int(settings_pack::cache_size, 2); +#endif + } + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [&num_suggests](lt::alert const* a, lt::session&) { + if (auto pl = alert_cast(a)) + { + if (pl->direction == peer_log_alert::outgoing_message + && pl->event_type == std::string("SUGGEST")) + { + ++num_suggests; + } + } + } + // terminate + , [](int const ticks, lt::session&) -> bool + { + if (ticks > 500) + { + return true; + } + return false; + }); + + // for now, just make sure we send any suggests at all. This feature is + // experimental and it's not entirely clear it's correct or how to verify + // that it does what it's supposed to do. + // perhaps a better way would be to look at piece upload distribution over + // time + TEST_CHECK(num_suggests > 0); +} +#endif + +TORRENT_TEST(utp_only) +{ + setup_swarm(2, swarm_test::download + // add session + , [](lt::settings_pack& pack) { + pack.set_bool(settings_pack::enable_incoming_utp, true); + pack.set_bool(settings_pack::enable_outgoing_utp, true); + pack.set_bool(settings_pack::enable_incoming_tcp, false); + pack.set_bool(settings_pack::enable_outgoing_tcp, false); + } + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [](lt::alert const*, lt::session&) {} + // terminate + , [](int const ticks, lt::session& ses) -> bool + { + if (ticks > 80) + { + TEST_ERROR("timeout"); + return true; + } + if (!is_seed(ses)) return false; + return true; + }); +} + +void test_stop_start_download(swarm_test_t type, bool graceful) +{ + bool paused_once = false; + bool resumed = false; + + setup_swarm(3, type + // add session + , [](lt::settings_pack& pack) { + // this test will pause and resume the torrent immediately, we expect + // to reconnect immediately too, so disable the min reconnect time + // limit. + pack.set_int(settings_pack::min_reconnect_time, 0); + } + // add torrent + , [](lt::add_torrent_params&) { + + } + // on alert + , [&](lt::alert const* a, lt::session& ses) { + + if (lt::alert_cast(a)) + add_extra_peers(ses); + + if (auto tp = lt::alert_cast(a)) + { + TEST_EQUAL(resumed, false); + std::printf("\nSTART\n\n"); + tp->handle.resume(); + resumed = true; + } + } + // terminate + , [&](int const ticks, lt::session& ses) -> bool + { + if (paused_once == false) + { + auto st = get_status(ses); + const bool limit_reached = (type & swarm_test::download) + ? st.total_wanted_done > st.total_wanted / 2 + : st.total_payload_upload >= 3 * 16 * 1024; + + if (limit_reached) + { + std::printf("\nSTOP\n\n"); + auto h = ses.get_torrents()[0]; + h.pause(graceful ? torrent_handle::graceful_pause : pause_flags_t{}); + paused_once = true; + } + } + + std::printf("tick: %d\n", ticks); + + const int timeout = (type & swarm_test::download) ? 22 : 100; + if (ticks > timeout) + { + TEST_ERROR("timeout"); + return true; + } + if (type & swarm_test::upload) return false; + if (!is_seed(ses)) return false; + std::printf("completed in %d ticks\n", ticks); + return true; + }); + + TEST_EQUAL(paused_once, true); + TEST_EQUAL(resumed, true); +} + +TORRENT_TEST(stop_start_download) +{ + test_stop_start_download(swarm_test::download, false); +} + +TORRENT_TEST(stop_start_download_graceful) +{ + test_stop_start_download(swarm_test::download, true); +} + +TORRENT_TEST(stop_start_download_graceful_no_peers) +{ + bool paused_once = false; + bool resumed = false; + + setup_swarm(1, swarm_test::download + // add session + , [](lt::settings_pack&) {} + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [&](lt::alert const* a, lt::session&) { + if (auto tp = lt::alert_cast(a)) + { + TEST_EQUAL(resumed, false); + std::printf("\nSTART\n\n"); + tp->handle.resume(); + resumed = true; + } + } + // terminate + , [&](int const ticks, lt::session& ses) -> bool + { + if (paused_once == false + && ticks == 6) + { + std::printf("\nSTOP\n\n"); + auto h = ses.get_torrents()[0]; + h.pause(torrent_handle::graceful_pause); + paused_once = true; + } + + std::printf("tick: %d\n", ticks); + + // when there's only one node (i.e. no peers) we won't ever download + // the torrent. It's just a test to make sure we still get the + // torrent_paused_alert + return ticks > 60; + }); + + TEST_EQUAL(paused_once, true); + TEST_EQUAL(resumed, true); +} + + +TORRENT_TEST(stop_start_seed) +{ + test_stop_start_download(swarm_test::upload, false); +} + +TORRENT_TEST(stop_start_seed_graceful) +{ + test_stop_start_download(swarm_test::upload, true); +} + +TORRENT_TEST(shutdown) +{ + setup_swarm(4, swarm_test::download + // add session + , [](lt::settings_pack&) {} + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [](lt::alert const*, lt::session&) {} + // terminate + , [](int, lt::session& ses) -> bool + { + if (completed_pieces(ses) == 0) return false; + TEST_EQUAL(is_seed(ses), false); + return true; + }); +} + +// make the delays on the connections unreasonable long, so libtorrent times-out +// the connection attempts +struct timeout_config : sim::default_config +{ + virtual sim::route incoming_route(lt::address ip) override + { + auto it = m_incoming.find(ip); + if (it != m_incoming.end()) return sim::route().append(it->second); + it = m_incoming.insert(it, std::make_pair(ip, std::make_shared( + m_sim->get_io_context() + , 1000 + , lt::duration_cast(seconds(10)) + , 1000, "packet-loss modem in"))); + return sim::route().append(it->second); + } + + virtual sim::route outgoing_route(lt::address ip) override + { + auto it = m_outgoing.find(ip); + if (it != m_outgoing.end()) return sim::route().append(it->second); + it = m_outgoing.insert(it, std::make_pair(ip, std::make_shared( + m_sim->get_io_context(), 1000 + , lt::duration_cast(seconds(5)), 200 * 1000, "packet-loss out"))); + return sim::route().append(it->second); + } +}; + +// make sure peers that are no longer alive are handled correctly. +TORRENT_TEST(dead_peers) +{ + int num_connect_timeout = 0; + + timeout_config network_cfg; + sim::simulation sim{network_cfg}; + setup_swarm(1, swarm_test::download, sim + // add session + , [](lt::settings_pack& p) { + p.set_int(settings_pack::peer_connect_timeout, 1); + } + // add torrent + , [](lt::add_torrent_params& params) { + params.peers.assign({ + ep("66.66.66.60", 9999) + , ep("66.66.66.61", 9999) + , ep("66.66.66.62", 9999) + }); + } + // on alert + , [&](lt::alert const* a, lt::session&) { + auto* e = alert_cast(a); + if (e + && e->op == operation_t::connect + && e->error == error_code(errors::timed_out)) + { + ++num_connect_timeout; + } + } + // terminate + , [](int t, lt::session&) -> bool + { return t > 100; }); + + TEST_EQUAL(num_connect_timeout, 3); +} + +// the address 50.0.0.1 sits behind a NAT. All of its outgoing connections have +// their source address rewritten to 51.51.51.51 +struct nat_config : sim::default_config +{ + nat_config() : m_nat_hop(std::make_shared(addr("51.51.51.51"))) {} + + sim::route outgoing_route(lt::address ip) override + { + // This is extremely simplistic. It will simply alter the percieved source + // IP of the connecting client. + sim::route r; + if (ip == addr("50.0.0.1")) r.append(m_nat_hop); + return r; + } + std::shared_ptr m_nat_hop; +}; + +TORRENT_TEST(self_connect) +{ + int num_self_connection_disconnects = 0; + + nat_config network_cfg; + sim::simulation sim{network_cfg}; + + setup_swarm(1, swarm_test::download, sim + // add session + , [](lt::settings_pack& p) { + p.set_bool(settings_pack::enable_incoming_utp, false); + p.set_bool(settings_pack::enable_outgoing_utp, false); + } + // add torrent + , [](lt::add_torrent_params& params) { + // this is our own address and listen port, just to make sure we get + // ourself as a peer (which normally happens one way or another in the + // wild) + params.peers.assign({ep("50.0.0.1", 6881)}); + } + // on alert + , [&](lt::alert const* a, lt::session&) { + auto* e = alert_cast(a); + if (e + && e->op == operation_t::bittorrent + && e->error == error_code(errors::self_connection)) + { + ++num_self_connection_disconnects; + } + } + // terminate + , [](int t, lt::session&) -> bool + { return t > 100; }); + + TEST_EQUAL(num_self_connection_disconnects, 1); +} + +TORRENT_TEST(delete_files) +{ + std::string save_path; + + setup_swarm(2, swarm_test::download + // add session + , [](lt::settings_pack&) {} + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [](lt::alert const*, lt::session&) {} + // terminate + , [&save_path](int, lt::session& ses) -> bool + { + if (completed_pieces(ses) == 0) return false; + + auto h = ses.get_torrents()[0]; + save_path = h.status().save_path; + ses.remove_torrent(h, session::delete_files); + return true; + }); + + // assert the file is no longer there + file_status st; + error_code ec; + stat_file(combine_path(save_path, "temporary"), &st, ec); + std::printf("expecting \"%s/temporary\" to NOT exist [%s | %s]\n" + , save_path.c_str() + , ec.category().name() + , ec.message().c_str()); + TEST_EQUAL(ec, error_code(boost::system::errc::no_such_file_or_directory, system_category())); +} + +TORRENT_TEST(delete_partfile) +{ + std::string save_path; + setup_swarm(2, swarm_test::download | swarm_test::real_disk + // add session + , [](lt::settings_pack&) {} + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [](lt::alert const*, lt::session&) {} + // terminate + , [&save_path](int, lt::session& ses) -> bool + { + if (completed_pieces(ses) == 0) return false; + + auto h = ses.get_torrents()[0]; + save_path = h.status().save_path; + ses.remove_torrent(h, session::delete_partfile); + return true; + }); + // assert the file *is* still there + file_status st; + error_code ec; + stat_file(combine_path(save_path, "temporary"), &st, ec); + std::printf("expecting \"%s/temporary\" to exist [%s]\n", save_path.c_str() + , ec.message().c_str()); + TEST_CHECK(!ec); +} + +TORRENT_TEST(torrent_completed_alert) +{ + int num_file_completed = false; + + setup_swarm(2, swarm_test::download + // add session + , [](lt::settings_pack& pack) + { + pack.set_int(lt::settings_pack::alert_mask, alert_category::file_progress); + } + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [&](lt::alert const* a, lt::session&) + { + auto tc = alert_cast(a); + if (tc == nullptr) return; + ++num_file_completed; + } + // terminate + , [](int ticks, lt::session& ses) -> bool + { + if (ticks > 80) + { + TEST_ERROR("timeout"); + return true; + } + if (!is_seed(ses)) return false; + printf("completed in %d ticks\n", ticks); + return true; + }); + + TEST_EQUAL(num_file_completed, 1); +} + +TORRENT_TEST(block_uploaded_alert) +{ + // blocks[piece count][number of blocks per piece] (each block's element will + // be set to true when a block_uploaded_alert alert is received for that block) + std::vector> blocks; + + setup_swarm(2, swarm_test::upload + // add session + , [](lt::settings_pack& pack) + { + pack.set_int(lt::settings_pack::alert_mask, + alert_category::upload | alert_category::status); + } + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [&](lt::alert const* a, lt::session&) { + if (auto at = lt::alert_cast(a)) + { + // init blocks vector, MUST happen before any block_uploaded_alert alerts + int blocks_per_piece = at->handle.torrent_file()->piece_length() / 0x4000; + blocks.resize(at->handle.torrent_file()->num_pieces(), std::vector(blocks_per_piece, false)); + } + else if (auto ua = lt::alert_cast(a)) + { + TEST_EQUAL(blocks[static_cast(ua->piece_index)][ua->block_index], false); + blocks[static_cast(ua->piece_index)][ua->block_index] = true; + } + } + // terminate + , [](int, lt::session&) -> bool + { return false; }); + + // ensure a block_uploaded_alert was received for each block in the torrent + TEST_CHECK(std::all_of(blocks.begin(), blocks.end(), + [](std::vector const& piece_row) { + return std::all_of(piece_row.begin(), piece_row.end(), + [](bool upload_alert_received) { + return upload_alert_received; + } + ); + } + )); +} + +// template for testing running swarms with edge case settings +template +void test_settings(SettingsFun fun) +{ + setup_swarm(2, swarm_test::download + // add session + , fun + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [](lt::alert const*, lt::session&) {} + // terminate + , [](int ticks, lt::session& ses) -> bool + { + if (ticks > 89) + { + TEST_ERROR("timeout"); + return true; + } + if (!is_seed(ses)) return false; + return true; + }); +} + +TORRENT_TEST(unlimited_connections) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_int(settings_pack::connections_limit, std::numeric_limits::max()); } + ); +} + +TORRENT_TEST(default_connections_limit) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_int(settings_pack::connections_limit, 0); } + ); +} + +TORRENT_TEST(default_connections_limit_negative) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_int(settings_pack::connections_limit, -1); } + ); +} + + +TORRENT_TEST(redundant_have) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_bool(settings_pack::send_redundant_have, false); } + ); +} + +#if TORRENT_ABI_VERSION == 1 +TORRENT_TEST(lazy_bitfields) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_bool(settings_pack::lazy_bitfields, true); } + ); +} +#endif + +TORRENT_TEST(prioritize_partial_pieces) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_bool(settings_pack::prioritize_partial_pieces, true); } + ); +} + +TORRENT_TEST(active_downloads) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_int(settings_pack::active_downloads, std::numeric_limits::max()); } + ); +} + +TORRENT_TEST(active_seeds) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_int(settings_pack::active_seeds, std::numeric_limits::max()); } + ); +} + +TORRENT_TEST(active_seeds_negative) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_int(settings_pack::active_seeds, -1); } + ); +} + +TORRENT_TEST(active_limit) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_int(settings_pack::active_limit, std::numeric_limits::max()); } + ); +} + +TORRENT_TEST(active_limit_negative) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_int(settings_pack::active_limit, -1); } + ); +} + +TORRENT_TEST(upload_rate_limit) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_int(settings_pack::upload_rate_limit, std::numeric_limits::max()); } + ); +} + +TORRENT_TEST(upload_rate_limit_negative) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_int(settings_pack::upload_rate_limit, -1); } + ); +} + +TORRENT_TEST(download_rate_limit) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_int(settings_pack::download_rate_limit, std::numeric_limits::max()); } + ); +} + +TORRENT_TEST(download_rate_limit_negative) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_int(settings_pack::download_rate_limit, -1); } + ); +} + +TORRENT_TEST(unchoke_slots_limit) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_int(settings_pack::unchoke_slots_limit, std::numeric_limits::max()); } + ); +} + +TORRENT_TEST(unchoke_slots_limit_negative) +{ + test_settings([](lt::settings_pack& pack) { + pack.set_int(settings_pack::unchoke_slots_limit, -1); + pack.set_int(settings_pack::choking_algorithm, settings_pack::fixed_slots_choker); + }); +} + +TORRENT_TEST(settings_stress_test) +{ + std::array const settings{{ + settings_pack::unchoke_slots_limit, + settings_pack::connections_limit, + settings_pack::predictive_piece_announce, + settings_pack::allow_multiple_connections_per_ip, + settings_pack::send_redundant_have, + settings_pack::rate_limit_ip_overhead, + settings_pack::rate_limit_ip_overhead, + settings_pack::anonymous_mode, +// settings_pack::enable_upnp, +// settings_pack::enable_natpmp, + settings_pack::enable_lsd, + settings_pack::enable_ip_notifier, + settings_pack::piece_extent_affinity, + }}; + std::array const values{{-1, 0, 1, std::numeric_limits::max()}}; + + for (auto t : { swarm_test::download, swarm_test::upload}) + { + for (auto s1 : settings) + { + for (auto s2 : settings) + { + if (s1 == s2) continue; + + setup_swarm(2, t + // add session + , [](lt::settings_pack& p) { + p.set_int(settings_pack::choking_algorithm, settings_pack::fixed_slots_choker); + } + // add torrent + , [](lt::add_torrent_params& params) {} + // on alert + , [](lt::alert const*, lt::session&) {} + // terminate + , [&](int tick, lt::session& session) -> bool + { + int const s = (tick & 1) ? s2 : s1; + settings_pack p; + if ((s & settings_pack::type_mask) == settings_pack::bool_type_base) + p.set_bool(s, bool(tick & 2)); + else + p.set_int(s, values[(tick >> 1) % values.size()]); + session.apply_settings(std::move(p)); + return tick > int(settings.size() * values.size() * 2); + }); + } + } + } +} + +TORRENT_TEST(pex) +{ + // we create 3 nodes. Node 0 seeds and node 1 and 2 are downloaders. + // node 0 is initially only connected to node 1. The test ensures that node + // 0 eventually is connected to node 2, as it should have been introduced + // via PEX + + dsl_config network_cfg; + sim::simulation sim{network_cfg}; + + asio::io_context ios(sim); + lt::time_point start_time(lt::clock_type::now()); + + std::vector> nodes; + std::vector> io_service; + std::vector zombies; + lt::deadline_timer timer(ios); + + lt::error_code ec; + int const swarm_id = unit_test::test_counter(); + std::string path = save_path(swarm_id, 0); + + lt::create_directory(path, ec); + if (ec) std::printf("failed to create directory: \"%s\": %s\n" + , path.c_str(), ec.message().c_str()); + std::ofstream file(lt::combine_path(path, "temporary").c_str()); + auto ti = ::create_torrent(&file, "temporary", 0x4000, 50, false); + file.close(); + + int const num_nodes = 3; + + bool done = false; + + // session 0 is the seeding one. + // the IPs are 50.0.0.1, 50.0.0.2 and 50.0.0.3 + for (int i = 0; i < num_nodes; ++i) + { + // create a new io_service + char ep[30]; + std::snprintf(ep, sizeof(ep), "50.0.%d.%d", (i + 1) >> 8, (i + 1) & 0xff); + io_service.push_back(std::make_shared(sim, addr(ep))); + + lt::settings_pack pack = settings(); + + // make sure the sessions have different peer ids + lt::peer_id pid; + lt::aux::random_bytes(pid); + pack.set_str(lt::settings_pack::peer_fingerprint, pid.to_string()); + std::shared_ptr ses = + std::make_shared(pack, *io_service.back()); + nodes.push_back(ses); + + lt::add_torrent_params p; + p.flags &= ~lt::torrent_flags::paused; + p.flags &= ~lt::torrent_flags::auto_managed; + + // node 0 and 1 are downloaders and node 2 is a seed + // save path 0 is where the files are, so that's for seeds + // It's important that node 1 and 2 want to stay connected, otherwise + // node 1 won't be able to gossip about 2 to 0. + p.save_path = save_path(swarm_id, i > 1 ? 0 : 1); + p.ti = ti; + ses->async_add_torrent(p); + + ses->set_alert_notify([&, i]() { + // this function is called inside libtorrent and we cannot perform work + // immediately in it. We have to notify the outside to pull all the alerts + post(*io_service[i], [&,i]() + { + lt::session* ses = nodes[i].get(); + + // when shutting down, we may have destructed the session + if (ses == nullptr) return; + + std::vector alerts; + ses->pop_alerts(&alerts); + + for (lt::alert* a : alerts) + { + // only print alerts from the session under test + lt::time_duration d = a->timestamp() - start_time; + std::uint32_t const millis = std::uint32_t( + lt::duration_cast(d).count()); + + if (i == 0) { + std::printf("%4d.%03d: %-25s %s\n" + , millis / 1000, millis % 1000 + , a->what() + , a->message().c_str()); + } + + // if a torrent was added save the torrent handle + if (lt::add_torrent_alert* at = lt::alert_cast(a)) + { + lt::torrent_handle h = at->handle; + + if (i == 0) + { + // node only connects to node 1 + h.connect_peer(lt::tcp::endpoint(addr("50.0.0.2"), 6881)); + } + else + { + // other nodes connect to each other + for (int k = 1; k < num_nodes; ++k) + { + char ep[30]; + std::snprintf(ep, sizeof(ep), "50.0.%d.%d" + , (k + 1) >> 8, (k + 1) & 0xff); + h.connect_peer(lt::tcp::endpoint(addr(ep), 6881)); + } + } + } + + if (i == 0) + { + // if node 0 was connected to 50.0.0.3, we're done + if (lt::peer_connect_alert* ca = lt::alert_cast(a)) + { + if (ca->endpoint.address() == addr("50.0.0.3")) + done = true; + } + if (lt::incoming_connection_alert* ca = lt::alert_cast(a)) + { + if (ca->endpoint.address() == addr("50.0.0.3")) + done = true; + } + } + } + }); + }); + } + + std::function on_done + = [&](lt::error_code const& ec) + { + if (ec) return; + + std::printf("TERMINATING\n"); + + // terminate simulation + for (int i = 0; i < int(nodes.size()); ++i) + { + zombies.push_back(nodes[i]->abort()); + nodes[i].reset(); + } + }; + + timer.expires_after(lt::seconds(65)); + timer.async_wait(on_done); + + sim.run(); + + TEST_EQUAL(done, true); +} + +// TODO: add test that makes sure a torrent in graceful pause mode won't make +// outgoing connections +// TODO: add test that makes sure a torrent in graceful pause mode won't accept +// incoming connections +// TODO: test the different storage allocation modes +// TODO: test contiguous buffer + + diff --git a/simulation/test_thread_pool.cpp b/simulation/test_thread_pool.cpp new file mode 100644 index 0000000..8baf33c --- /dev/null +++ b/simulation/test_thread_pool.cpp @@ -0,0 +1,216 @@ +/* + +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 "simulator/simulator.hpp" +#include "libtorrent/aux_/disk_io_thread_pool.hpp" +#include "libtorrent/io_context.hpp" +#include + +using lt::io_context; + +struct test_threads : lt::aux::pool_thread_interface +{ + test_threads() {} + + void notify_all() override { m_cond.notify_all(); } + void thread_fun(lt::aux::disk_io_thread_pool&, lt::executor_work_guard) override + { + std::unique_lock l(m_mutex); + for (;;) + { + m_pool->thread_idle(); + while (!m_pool->should_exit() && m_active_threads >= m_target_active_threads) + m_cond.wait(l); + m_pool->thread_active(); + + if (m_pool->try_thread_exit(std::this_thread::get_id())) + break; + + if (m_active_threads < m_target_active_threads) + { + ++m_active_threads; + while (!m_pool->should_exit() && m_active_threads <= m_target_active_threads) + m_cond.wait(l); + --m_active_threads; + } + + if (m_pool->try_thread_exit(std::this_thread::get_id())) + break; + } + + l.unlock(); + m_exit_cond.notify_all(); + } + + // change the number of active threads and wait for the threads + // to settle at the new value + void set_active_threads(int target) + { + std::unique_lock l(m_mutex); + assert(target <= m_pool->num_threads()); + m_target_active_threads = target; + while (m_active_threads != m_target_active_threads) + { + l.unlock(); + m_cond.notify_all(); + std::this_thread::yield(); + l.lock(); + } + } + + // this is to close a race between a thread exiting and a test checking the + // thread count + void wait_for_thread_exit(int num_threads) + { + std::unique_lock l(m_mutex); + m_exit_cond.wait_for(l, std::chrono::seconds(30), [&]() + { + return m_pool->num_threads() == num_threads; + }); + } + + lt::aux::disk_io_thread_pool* m_pool; + std::mutex m_mutex; + std::condition_variable m_cond; + std::condition_variable m_exit_cond; + + // must hold m_mutex to access + int m_active_threads = 0; + // must hold m_mutex to access + int m_target_active_threads = 0; +}; + +/* +TORRENT_TEST(disk_io_thread_pool_idle_reaping) +{ + sim::default_config cfg; + sim::simulation sim{ cfg }; + + test_threads threads; + sim::asio::io_context ios(sim); + lt::aux::disk_io_thread_pool pool(threads, ios); + threads.m_pool = &pool; + pool.set_max_threads(3); + pool.job_queued(3); + TEST_EQUAL(pool.num_threads(), 3); + // make sure all the threads are up and settled in the active state + threads.set_active_threads(3); + + // first just kill one thread + threads.set_active_threads(2); + lt::deadline_timer idle_delay(ios); + // the thread will be killed the second time the reaper runs and we need + // to wait one extra minute to make sure the check runs after the reaper + idle_delay.expires_after(std::chrono::minutes(3)); + idle_delay.async_wait([&](lt::error_code const&) + { + // this is a kludge to work around a race between the thread + // exiting and checking the number of threads + // in production we only check num_threads from the disk I/O threads + // so there are no race problems there + threads.wait_for_thread_exit(2); + TEST_EQUAL(pool.num_threads(), 2); + sim.stop(); + }); + sim.run(); + sim.restart(); + + // now kill the rest + threads.set_active_threads(0); + idle_delay.expires_after(std::chrono::minutes(3)); + idle_delay.async_wait([&](lt::error_code const&) + { + // see comment above about this kludge + threads.wait_for_thread_exit(0); + TEST_EQUAL(pool.num_threads(), 0); + }); + sim.run(); +} +*/ + +TORRENT_TEST(disk_io_thread_pool_abort_wait) +{ + sim::default_config cfg; + sim::simulation sim{ cfg }; + + test_threads threads; + sim::asio::io_context ios(sim); + lt::aux::disk_io_thread_pool pool(threads, ios); + threads.m_pool = &pool; + pool.set_max_threads(3); + pool.job_queued(3); + TEST_EQUAL(pool.num_threads(), 3); + pool.abort(true); + TEST_EQUAL(pool.num_threads(), 0); +} + +#if 0 +// disabled for now because io_context::work doesn't work under the simulator +// and we need it to stop this test from exiting prematurely +TORRENT_TEST(disk_io_thread_pool_abort_no_wait) +{ + sim::default_config cfg; + sim::simulation sim{ cfg }; + + test_threads threads; + sim::asio::io_context ios(sim); + lt::aux::disk_io_thread_pool pool(threads, ios); + threads.m_pool = &pool; + pool.set_max_threads(3); + pool.job_queued(3); + TEST_EQUAL(pool.num_threads(), 3); + pool.abort(false); + TEST_EQUAL(pool.num_threads(), 0); + sim.run(); +} +#endif + +TORRENT_TEST(disk_io_thread_pool_max_threads) +{ + sim::default_config cfg; + sim::simulation sim{ cfg }; + + test_threads threads; + sim::asio::io_context ios(sim); + lt::aux::disk_io_thread_pool pool(threads, ios); + threads.m_pool = &pool; + // first check that the thread limit is respected when adding jobs + pool.set_max_threads(3); + pool.job_queued(4); + TEST_EQUAL(pool.num_threads(), 3); + // now check that the number of threads is reduced when the max threads is reduced + pool.set_max_threads(2); + // see comment above about this kludge + threads.wait_for_thread_exit(2); + TEST_EQUAL(pool.num_threads(), 2); +} diff --git a/simulation/test_timeout.cpp b/simulation/test_timeout.cpp new file mode 100644 index 0000000..5305a15 --- /dev/null +++ b/simulation/test_timeout.cpp @@ -0,0 +1,308 @@ +/* + +Copyright (c) 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/torrent_handle.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/disabled_disk_io.hpp" +#include "settings.hpp" +#include "fake_peer.hpp" +#include "utils.hpp" +#include "setup_transfer.hpp" +#include "create_torrent.hpp" +#include "simulator/simulator.hpp" +#include "simulator/utils.hpp" +#include "simulator/queue.hpp" + +using namespace sim; +using namespace lt; + +using disconnects_t = std::vector>; + +disconnects_t test_timeout(sim::configuration& cfg) +{ + sim::simulation sim{cfg}; + auto const start_time = lt::clock_type::now(); + std::unique_ptr ios = make_io_context(sim, 0); + lt::session_proxy zombie; + + lt::session_params sp; + sp.settings = settings(); + sp.settings.set_int(settings_pack::alert_mask, alert_category::all & ~alert_category::stats); + sp.settings.set_bool(settings_pack::disable_hash_checks, true); + sp.disk_io_constructor = lt::disabled_disk_io_constructor; + + // create session + std::shared_ptr ses = std::make_shared(sp, *ios); + + fake_peer p1(sim, "60.0.0.0"); + + // add torrent + lt::add_torrent_params params = ::create_torrent(0, false); + params.flags &= ~lt::torrent_flags::auto_managed; + params.flags &= ~lt::torrent_flags::paused; + params.flags |= lt::torrent_flags::seed_mode; + lt::sha1_hash info_hash = params.ti->info_hash(); + ses->async_add_torrent(std::move(params)); + + disconnects_t disconnects; + + lt::torrent_handle h; + print_alerts(*ses, [&](lt::session& ses, lt::alert const* a) { + if (auto* at = lt::alert_cast(a)) + { + h = at->handle; + + p1.connect_to(ep("50.0.0.1", 6881), info_hash); + p1.send_interested(); + p1.send_request(piece_index_t{0}, 0); + } + else if (auto* pd = lt::alert_cast(a)) + { + disconnects.emplace_back(duration_cast(pd->timestamp() - start_time), pd->error); + } + }); + + // set up a timer to fire later, to shut down + sim::timer t2(sim, lt::seconds(400) + , [&](boost::system::error_code const&) + { + // shut down + zombie = ses->abort(); + ses.reset(); + }); + + sim.run(); + + return disconnects; +} + +// the inactive timeout is 60 seconds. If we don't receive a request from a peer +// that's interested in us for 60 seconds, we disconnect them. +TORRENT_TEST(no_request_timeout) +{ + sim::default_config network_cfg; + auto disconnects = test_timeout(network_cfg); + TEST_CHECK((disconnects == disconnects_t{{lt::seconds{60}, lt::errors::timed_out_no_request}})); +} + +struct slow_upload : sim::default_config +{ + sim::route outgoing_route(asio::ip::address ip) override + { + // only affect the libtorrent instance, not the fake peer + if (ip != addr("50.0.0.1")) return sim::default_config::outgoing_route(ip); + + int const rate = 1; + + using duration = sim::chrono::high_resolution_clock::duration; + + auto it = m_outgoing.find(ip); + if (it != m_outgoing.end()) return sim::route().append(it->second); + it = m_outgoing.insert(it, std::make_pair(ip, std::make_shared( + std::ref(m_sim->get_io_context()) + , rate * 1000 + , lt::duration_cast(milliseconds(rate / 2)) + , 200 * 1000, "slow upload rate"))); + return sim::route().append(it->second); + } +}; + +// if the upload capacity is so low, that we're still trying to respond to the +// last request, we don't trigger the inactivity timeout, we don't expect the +// other peer to keep requesting more pieces before receiving the previous ones +TORRENT_TEST(no_request_timeout_slow_upload) +{ + slow_upload cfg; + auto disconnects = test_timeout(cfg); + TEST_CHECK((disconnects == disconnects_t{{lt::seconds{73}, lt::errors::timed_out_no_request}})); +} + +disconnects_t test_no_interest_timeout(int const num_peers + , lt::session_params sp + , bool const redundant_no_interest) +{ + sim::default_config cfg; + sim::simulation sim{cfg}; + auto const start_time = lt::clock_type::now(); + std::unique_ptr ios = make_io_context(sim, 0); + lt::session_proxy zombie; + + sp.settings.set_int(settings_pack::alert_mask, alert_category::all & ~alert_category::stats); + + // create session + std::shared_ptr ses = std::make_shared(sp, *ios); + + std::vector> peers; + for (int i = 0; i < num_peers; ++i) + { + char ip[50]; + std::snprintf(ip, sizeof(ip), "60.0.0.%d", i + 1); + peers.emplace_back(new fake_peer(sim, ip)); + } + + // add torrent + lt::add_torrent_params params = ::create_torrent(0, false); + params.flags &= ~lt::torrent_flags::auto_managed; + params.flags &= ~lt::torrent_flags::paused; + lt::sha1_hash info_hash = params.ti->info_hash(); + ses->async_add_torrent(std::move(params)); + + disconnects_t disconnects; + + lt::torrent_handle h; + print_alerts(*ses, [&](lt::session& ses, lt::alert const* a) { + if (auto* at = lt::alert_cast(a)) + { + h = at->handle; + for (auto& p : peers) + p->connect_to(ep("50.0.0.1", 6881), info_hash); + } + else if (auto* pd = lt::alert_cast(a)) + { + disconnects.emplace_back(duration_cast(pd->timestamp() - start_time), pd->error); + } + }); + + std::function keep_alive + = [&](boost::system::error_code const&) + { + for (auto& p : peers) + { + p->send_keepalive(); + p->flush_send_buffer(); + } + }; + + std::function send_not_interested + = [&](boost::system::error_code const&) + { + for (auto& p : peers) + { + p->send_not_interested(); + p->flush_send_buffer(); + } + }; + + auto const& tick = redundant_no_interest ? send_not_interested : keep_alive; + + sim::timer t3(sim, lt::seconds(100), tick); + sim::timer t4(sim, lt::seconds(200), tick); + sim::timer t5(sim, lt::seconds(300), tick); + sim::timer t6(sim, lt::seconds(400), tick); + sim::timer t7(sim, lt::seconds(500), tick); + sim::timer t8(sim, lt::seconds(599), tick); + + // set up a timer to fire later, to shut down + sim::timer t2(sim, lt::seconds(700) + , [&](boost::system::error_code const&) + { + // shut down + zombie = ses->abort(); + ses.reset(); + }); + + sim.run(); + + return disconnects; +} + +// if a peer is not interested in us, and we're not interested in it for long +// enoguh, we disconenct it, but only if we are close to peer connection capacity +TORRENT_TEST(no_interest_timeout) +{ + // with 10 peers, we're close enough to the connection limit to enable + // inactivity timeout + + lt::session_params sp; + sp.disk_io_constructor = lt::disabled_disk_io_constructor; + sp.settings = settings(); + sp.settings.set_int(settings_pack::connections_limit, 15); + auto disconnects = test_no_interest_timeout(10, std::move(sp), false); + TEST_EQUAL(disconnects.size(), 10); + for (auto const& e : disconnects) + { + TEST_CHECK(e.first == lt::seconds{600}); + TEST_CHECK(e.second == lt::errors::timed_out_no_interest); + } +} + +TORRENT_TEST(no_interest_timeout_redundant_not_interested) +{ + // even though the peers keep sending not-interested, our clock should not + // restart + lt::session_params sp; + sp.disk_io_constructor = lt::disabled_disk_io_constructor; + sp.settings = settings(); + sp.settings.set_int(settings_pack::connections_limit, 15); + auto disconnects = test_no_interest_timeout(10, std::move(sp), true); + TEST_EQUAL(disconnects.size(), 10); + for (auto const& e : disconnects) + { + TEST_CHECK(e.first == lt::seconds{600}); + TEST_CHECK(e.second == lt::errors::timed_out_no_interest); + } +} + +TORRENT_TEST(no_interest_timeout_zero) +{ + // if we set inactivity_timeout to 0, all peers should be disconnected + // immediately + lt::session_params sp; + sp.disk_io_constructor = lt::disabled_disk_io_constructor; + sp.settings = settings(); + sp.settings.set_int(settings_pack::connections_limit, 15); + sp.settings.set_int(settings_pack::inactivity_timeout, 0); + auto disconnects = test_no_interest_timeout(10, std::move(sp), false); + TEST_EQUAL(disconnects.size(), 10); + for (auto const& e : disconnects) + { + TEST_CHECK(e.first == lt::seconds{0}); + TEST_CHECK(e.second == lt::errors::timed_out_no_interest); + } +} + +TORRENT_TEST(no_interest_timeout_few_peers) +{ + // with a higher connections limit we're not close enough to enable + // inactivity timeout + lt::session_params sp; + sp.disk_io_constructor = lt::disabled_disk_io_constructor; + sp.settings = settings(); + sp.settings.set_int(settings_pack::connections_limit, 20); + auto disconnects = test_no_interest_timeout(10, std::move(sp), false); + TEST_CHECK(disconnects == disconnects_t{}); +} diff --git a/simulation/test_torrent_status.cpp b/simulation/test_torrent_status.cpp new file mode 100644 index 0000000..7451990 --- /dev/null +++ b/simulation/test_torrent_status.cpp @@ -0,0 +1,570 @@ +/* + +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 "setup_swarm.hpp" +#include "utils.hpp" +#include "simulator/simulator.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/session.hpp" + +#include + +using namespace lt; +using namespace sim; + +time_point32 time_now() +{ + return lt::time_point_cast(lt::clock_type::now()); +} + +template +bool eq(Tp1 const lhs, Tp2 const rhs) +{ + return std::abs(lt::duration_cast(lhs - rhs).count()) <= 2; +} + +// this is a test for torrent_status time counters are correct +TORRENT_TEST(status_timers) +{ + lt::time_point32 start_time; + lt::torrent_handle handle; + bool ran_to_completion = false; + + setup_swarm(1, swarm_test::upload + // add session + , [](lt::settings_pack&) {} + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [&](lt::alert const* a, lt::session&) { + if (auto ta = alert_cast(a)) + { + TEST_CHECK(!handle.is_valid()); + start_time = time_now(); + handle = ta->handle; + } + } + // terminate + , [&](int ticks, lt::session&) -> bool + { + + // simulate 20 hours of uptime. Currently, the session_time and internal + // peer timestamps are 16 bits counting seconds, so they can only + // represent about 18 hours. The clock steps forward in 4 hour increments + // to stay within that range + if (ticks > 20 * 60 * 60) + { + ran_to_completion = true; + return true; + } + + // once an hour, verify that the timers seem correct + if ((ticks % 3600) == 0) + { + lt::time_point32 const now = time_now(); + // finish is 1 tick after start + auto const since_finish = duration_cast(now - start_time); + torrent_status st = handle.status(); + TEST_EQUAL(st.active_duration.count(), since_finish.count()); + TEST_EQUAL(st.seeding_duration.count(), since_finish.count()); + TEST_EQUAL(st.finished_duration.count(), since_finish.count()); + + // does not upload without peers + TEST_CHECK(st.last_upload == time_point(seconds(0))); + // does not download in seeding mode + TEST_CHECK(st.last_download == time_point(seconds(0))); + } + return false; + }); + TEST_CHECK(ran_to_completion); +} + +TORRENT_TEST(status_timers_last_upload) +{ + bool ran_to_completion = false; + + lt::torrent_handle handle; + + setup_swarm(2, swarm_test::upload + // add session + , [](lt::settings_pack&) {} + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [&](lt::alert const* a, lt::session&) { + if (auto ta = alert_cast(a)) + { + TEST_CHECK(!handle.is_valid()); + handle = ta->handle; + torrent_status st = handle.status(); + // test last upload and download state before wo go throgh + // torrent states + TEST_CHECK(st.last_upload == time_point(seconds(0))); + TEST_CHECK(st.last_download == time_point(seconds(0))); + } + } + // terminate + , [&](int ticks, lt::session&) -> bool + { + if (ticks > 10) + { + ran_to_completion = true; + return true; + } + + torrent_status st = handle.status(); + // upload time is 0 seconds behind now + TEST_CHECK(eq(st.last_upload, time_now())); + // does not download in seeding mode + TEST_CHECK(st.last_download == time_point(seconds(0))); + return false; + }); + TEST_CHECK(ran_to_completion); +} + +TORRENT_TEST(status_timers_time_shift_with_active_torrent) +{ + bool ran_to_completion = false; + + lt::torrent_handle handle; + seconds expected_active_duration = seconds(1); + bool tick_is_in_active_range = false; + int tick_check_intervall = 1; + + setup_swarm(1, swarm_test::upload + // add session + , [](lt::settings_pack&) {} + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [&](lt::alert const* a, lt::session&) { + if (auto ta = alert_cast(a)) + { + TEST_CHECK(!handle.is_valid()); + handle = ta->handle; + torrent_status st = handle.status(); + // test last upload and download state before wo go throgh + // torrent states + TEST_CHECK(st.last_download == time_point(seconds(0))); + TEST_CHECK(st.last_upload == time_point(seconds(0))); + } + } + // terminate + , [&](int ticks, lt::session&) -> bool + { + if(tick_is_in_active_range){ + // 1 second per tick + expected_active_duration++; + } + + switch(ticks) + { + case 0: + // torrent get ready for seeding on first tick, means time +1s + tick_is_in_active_range = true; + break; + case 1: + // pause after we did have the first upload tick + handle.pause(); + tick_is_in_active_range = false; + break; + case 64000: + // resume just before we hit the time shift handling + // this is needed to test what happend if we want to + // shift more time then we have active time because + // we shift 4 hours and have less then 1 hours active time + handle.resume(); + tick_is_in_active_range = true; + // don't check every tick + tick_check_intervall = 600; + break; + case 68000: + // simulate at least 68000 seconds because timestamps are + // 16 bits counting seconds + ran_to_completion = true; + return true; + } + + // verify that the timers seem correct + if (tick_is_in_active_range && (ticks % tick_check_intervall) == 0) + { + torrent_status st = handle.status(); + TEST_EQUAL(st.active_duration.count(), expected_active_duration.count()); + TEST_EQUAL(st.seeding_duration.count(), expected_active_duration.count()); + TEST_EQUAL(st.finished_duration.count(), expected_active_duration.count()); + // does not upload without peers + TEST_CHECK(st.last_upload == time_point(seconds(0))); + // does not download in seeding mode + TEST_CHECK(st.last_download == time_point(seconds(0))); + } + return false; + }); + TEST_CHECK(ran_to_completion); +} + +TORRENT_TEST(finish_time_shift_active) +{ + bool ran_to_completion = false; + + lt::torrent_handle handle; + seconds expected_active_duration = seconds(1); + bool tick_is_in_active_range = false; + + setup_swarm(1, swarm_test::upload + // add session + , [](lt::settings_pack&) {} + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [&](lt::alert const* a, lt::session&) { + if (auto ta = alert_cast(a)) + { + TEST_CHECK(!handle.is_valid()); + handle = ta->handle; + torrent_status st = handle.status(); + // test last upload and download state before wo go throgh + // torrent states + TEST_CHECK(st.last_download == time_point(seconds(0))); + TEST_CHECK(st.last_upload == time_point(seconds(0))); + } + } + // terminate + , [&](int ticks, lt::session&) -> bool + { + if(tick_is_in_active_range){ + // 1 second per tick + expected_active_duration++; + } + + switch(ticks) + { + case 0: + // torrent get ready for seeding on first tick, means time +1s + tick_is_in_active_range = true; + break; + case 7000: + // pause before 4 hours to get a become finish timestamp which + // will be clamped + handle.pause(); + // resume to get an become finish update + handle.resume(); + tick_is_in_active_range = true; + break; + case 70000: + // simulate at least 70000 seconds because timestamps are + // 16 bits counting seconds + ran_to_completion = true; + return true; + } + + // verify that the timers seem correct + if ((ticks % 3600) == 0) + { + torrent_status st = handle.status(); + TEST_EQUAL(st.active_duration.count(), expected_active_duration.count()); + TEST_EQUAL(st.seeding_duration.count(), expected_active_duration.count()); + TEST_EQUAL(st.finished_duration.count(), expected_active_duration.count()); + // does not upload without peers + TEST_CHECK(st.last_upload == time_point(seconds(0))); + // does not download in seeding mode + TEST_CHECK(st.last_download == time_point(seconds(0))); + } + return false; + }); + TEST_CHECK(ran_to_completion); +} + +TORRENT_TEST(finish_time_shift_paused) +{ + bool ran_to_completion = false; + + lt::torrent_handle handle; + seconds expected_active_duration = seconds(1); + bool tick_is_in_active_range = false; + + setup_swarm(1, swarm_test::upload + // add session + , [](lt::settings_pack&) {} + // add torrent + , [](lt::add_torrent_params&) {} + // on alert + , [&](lt::alert const* a, lt::session&) { + if (auto ta = alert_cast(a)) + { + TEST_CHECK(!handle.is_valid()); + handle = ta->handle; + torrent_status st = handle.status(); + // test last upload and download state before wo go throgh + // torrent states + TEST_CHECK(st.last_upload == time_point(seconds(0))); + TEST_CHECK(st.last_download == time_point(seconds(0))); + } + } + // terminate + , [&](int ticks, lt::session&) -> bool + { + if(tick_is_in_active_range){ + // 1 second per tick + expected_active_duration++; + } + + switch(ticks) + { + case 0: + // torrent get ready for seeding on first tick, means time +1s + tick_is_in_active_range = true; + break; + case 7000: + // pause before 4 hours to get a become finish timestamp which + // will be clamped + handle.pause(); + // resume to get an become finish update + handle.resume(); + // pause to test timeshift in paused state + handle.pause(); + tick_is_in_active_range = false; + break; + case 70000: + // simulate at least 70000 seconds because timestamps are + // 16 bits counting seconds + ran_to_completion = true; + return true; + } + + // verify that the timers seem correct + if (tick_is_in_active_range && (ticks % 3600) == 0) + { + torrent_status st = handle.status(); + TEST_EQUAL(st.active_duration.count(), expected_active_duration.count()); + TEST_EQUAL(st.seeding_duration.count(), expected_active_duration.count()); + TEST_EQUAL(st.finished_duration.count(), expected_active_duration.count()); + // does not upload without peers + TEST_CHECK(st.last_upload == time_point(seconds(0))); + // does not download in seeding mode + TEST_CHECK(st.last_download == time_point(seconds(0))); + } + return false; + }); + TEST_CHECK(ran_to_completion); +} + +// This test makes sure that adding a torrent causes no torrent related alert to +// be posted _before_ the add_torrent_alert, which is expected to always be the +// first +TORRENT_TEST(alert_order) +{ + bool received_add_torrent_alert = false; + int num_torrent_alerts = 0; + + lt::torrent_handle handle; + + setup_swarm(1, swarm_test::upload + // add session + , [](lt::settings_pack& sett) { + sett.set_int(settings_pack::alert_mask, alert_category::all); + } + // add torrent + , [](lt::add_torrent_params ) {} + // on alert + , [&](lt::alert const* a, lt::session&) { + if (auto ta = alert_cast(a)) + { + TEST_EQUAL(received_add_torrent_alert, false); + received_add_torrent_alert = true; + handle = ta->handle; + } + + if (auto ta = dynamic_cast(a)) + { + TEST_EQUAL(received_add_torrent_alert, true); + TEST_CHECK(handle == ta->handle); + ++num_torrent_alerts; + } + } + // terminate + , [&](int ticks, lt::session&) -> bool + { return ticks > 10; } + ); + + TEST_EQUAL(received_add_torrent_alert, true); + TEST_CHECK(num_torrent_alerts > 1); +} + +// this tests a torrent completing the download when `active_seeds` is set to 0. +// the torrent should be paused when it completes the download +TORRENT_TEST(active_timer_no_seed) +{ + lt::time_point32 start_time; + lt::torrent_handle handle; + bool ran_to_completion = false; + + int active_time = 0; + + setup_swarm(4, swarm_test::download + // add session + , [](lt::settings_pack& p ) { + p.set_int(settings_pack::active_seeds, 0); + p.set_bool(settings_pack::dont_count_slow_torrents, false); + } + // add torrent + , [](lt::add_torrent_params& p) { + p.flags |= torrent_flags::auto_managed; + } + // on alert + , [&](lt::alert const* a, lt::session&) { + if (auto ta = alert_cast(a)) + { + TEST_CHECK(!handle.is_valid()); + start_time = time_now(); + handle = ta->handle; + } + } + // terminate + , [&](int const ticks, lt::session& ses) -> bool + { + if (!is_seed(ses)) + { + ++active_time; + } + else + { + // some part of the simulation is not deterministic, and causes this to vary + // between platforms/compilers + TEST_CHECK(active_time >= 10); + TEST_CHECK(active_time <= 14); + } + + torrent_status st = handle.status(); + TEST_EQUAL(st.active_duration.count(), active_time); + TEST_EQUAL(st.seeding_duration.count(), 0); + TEST_EQUAL(st.finished_duration.count(), 0); + + // does not upload without peers + TEST_CHECK(st.last_upload == time_point(seconds(0))); + + if (ticks > 2 * 60) + { + ran_to_completion = true; + return true; + } + + return false; + }); + TEST_CHECK(ran_to_completion); +} + +template +void test_pause(PauseFun f) +{ + lt::time_point32 start_time; + lt::torrent_handle handle; + bool ran_to_completion = false; + + int const pause_time = 5; + + int active_time = 0; + + int paused_alert_count = 0; + + setup_swarm(5, swarm_test::download + // add session + , [](lt::settings_pack& p ) {} + // add torrent + , [](lt::add_torrent_params& p) {} + // on alert + , [&](lt::alert const* a, lt::session&) { + if (auto ta = alert_cast(a)) + { + TEST_CHECK(!handle.is_valid()); + start_time = time_now(); + handle = ta->handle; + } + if (alert_cast(a)) + { + ++paused_alert_count; + } + } + // terminate + , [&](int const ticks, lt::session& ses) -> bool + { + if (ticks == pause_time) + { + f(handle, ses); + } + if (ticks <= pause_time) + ++active_time; + + torrent_status st = handle.status(); + TEST_EQUAL(st.active_duration.count(), active_time); + TEST_EQUAL(st.seeding_duration.count(), 0); + TEST_EQUAL(st.finished_duration.count(), 0); + + // does not upload without peers + TEST_CHECK(st.last_upload == time_point(seconds(0))); + + if (ticks > 2 * 60) + { + ran_to_completion = true; + return true; + } + + return false; + }); + TEST_EQUAL(paused_alert_count, 1); + TEST_CHECK(ran_to_completion); +} + +TORRENT_TEST(active_timer_graceful_pause) +{ + test_pause([](lt::torrent_handle h, lt::session&) + { + h.pause(torrent_handle::graceful_pause); + }); +} + +TORRENT_TEST(active_timer_pause) +{ + test_pause([](lt::torrent_handle h, lt::session&) + { + h.pause(); + }); +} + +TORRENT_TEST(active_timer_session_pause) +{ + test_pause([](lt::torrent_handle, lt::session& s) + { + s.pause(); + }); +} diff --git a/simulation/test_tracker.cpp b/simulation/test_tracker.cpp new file mode 100644 index 0000000..ad86308 --- /dev/null +++ b/simulation/test_tracker.cpp @@ -0,0 +1,1839 @@ +/* + +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 "test.hpp" +#include "settings.hpp" +#include "setup_swarm.hpp" +#include "setup_transfer.hpp" // for addr() +#include "utils.hpp" // for print_alerts +#include "create_torrent.hpp" +#include "simulator/simulator.hpp" +#include "simulator/http_server.hpp" +#include "simulator/utils.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" // for is_v4 + +#include + +using namespace lt; +using namespace sim; + +using chrono::duration_cast; + +// seconds +const int duration = 10000; + +template +bool eq(Tp1 const lhs, Tp2 const rhs) +{ + return std::abs(lt::duration_cast(lhs - rhs).count()) <= 1; +} + +void test_interval(int interval) +{ + using sim::asio::ip::address_v4; + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + + bool ran_to_completion = false; + + sim::asio::io_context web_server(sim, make_address_v4("2.2.2.2")); + // listen on port 8080 + sim::http_server http(web_server, 8080); + + // the timestamps of all announces + std::vector announces; + + http.register_handler("/announce" + , [&announces,interval,&ran_to_completion](std::string /* method */ + , std::string /* req */ + , std::map&) + { + // don't collect events once we're done. We're not interested in the + // tracker stopped announce for instance + if (!ran_to_completion) + announces.push_back(lt::clock_type::now()); + + char response[500]; + int const size = std::snprintf(response, sizeof(response), "d8:intervali%de5:peers0:e", interval); + return sim::send_response(200, "OK", size) + response; + }); + + std::vector announce_alerts; + + lt::settings_pack default_settings = settings(); + // since the test tracker is only listening on IPv4 we need to configure the + // client to do the same so that the number of tracker_announce_alerts matches + // the number of announces seen by the tracker + default_settings.set_str(settings_pack::listen_interfaces, "0.0.0.0:6881"); + lt::add_torrent_params default_add_torrent; + + setup_swarm(1, swarm_test::upload, sim, default_settings, default_add_torrent + // add session + , [](lt::settings_pack&) {} + // add torrent + , [](lt::add_torrent_params& params) { + params.trackers.push_back("http://2.2.2.2:8080/announce"); + } + // on alert + , [&](lt::alert const* a, lt::session&) { + + if (ran_to_completion) return; + if (lt::alert_cast(a)) + { + announce_alerts.push_back(a->timestamp()); + } + } + // terminate + , [&](int const ticks, lt::session&) -> bool { + if (ticks > duration + 1) + { + ran_to_completion = true; + return true; + } + return false; + }); + + TEST_CHECK(ran_to_completion); + TEST_EQUAL(announce_alerts.size(), announces.size()); + TEST_CHECK(announces.size() % 2 == 0); + + lt::time_point last_announce = announces[0]; + lt::time_point last_alert = announce_alerts[0]; + for (int i = 2; i < int(announces.size()); i += 2) + { + // make sure the interval is within 1 second of what it's supposed to be + // (this accounts for network latencies, and the second-granularity + // timestamps) + TEST_CHECK(eq(duration_cast(announces[i] - last_announce), lt::seconds(interval))); + last_announce = announces[i]; + + TEST_CHECK(eq(duration_cast(announce_alerts[i] - last_alert), lt::seconds(interval))); + last_alert = announce_alerts[i]; + } +} + +template +std::vector test_event(swarm_test_t const type + , AddTorrent add_torrent + , OnAlert on_alert) +{ + using sim::asio::ip::address_v4; + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + + sim::asio::io_context web_server(sim, make_address_v4("2.2.2.2")); + // listen on port 8080 + sim::http_server http(web_server, 8080); + + // the request strings of all announces + std::vector announces; + + const int interval = 500; + + http.register_handler("/announce" + , [&](std::string method, std::string req + , std::map&) + { + TEST_EQUAL(method, "GET"); + announces.push_back(req); + + char response[500]; + int const size = std::snprintf(response, sizeof(response), "d8:intervali%de5:peers0:e", interval); + return sim::send_response(200, "OK", size) + response; + }); + + lt::settings_pack default_settings = settings(); + lt::add_torrent_params default_add_torrent; + + setup_swarm(2, type, sim, default_settings, default_add_torrent + // add session + , [](lt::settings_pack&) { } + // add torrent + , add_torrent + // on alert + , on_alert + // terminate + , [&](int const ticks, lt::session& ses) -> bool + { + return ticks > duration; + }); + + // this is some basic sanity checking of the announces that should always be + // true. + // the first announce should be event=started then no other announce should + // have event=started. + // only the last announce should have event=stopped. + TEST_CHECK(announces.size() > 2); + + // to keep things simple, just consider one of the v1 or v2 announces, since + // we use a hybrid torrent, we get double announces. + std::map> announces_ih; + for (auto&& a : announces) + { + auto const ih = a.find("info_hash="); + TEST_CHECK(ih != std::string::npos); + auto const key = a.substr(ih, 20); + announces_ih[key].push_back(std::move(a)); + } + + for (auto const& entry : announces_ih) + { + auto const& ann = entry.second; + TEST_CHECK(ann.size() > 2); + TEST_CHECK(ann.front().find("&event=started") != std::string::npos); + for (auto const& a : span(ann).subspan(1)) + TEST_CHECK(a.find("&event=started") == std::string::npos); + + TEST_CHECK(ann.back().find("&event=stopped") != std::string::npos); + for (auto const& a : span(ann).first(ann.size() - 1)) + TEST_CHECK(a.find("&event=stopped") == std::string::npos); + } + return announces_ih.begin()->second; +} + +TORRENT_TEST(event_completed_downloading) +{ + auto const announces = test_event(swarm_test::download + , [](lt::add_torrent_params& params) { + params.trackers.push_back("http://2.2.2.2:8080/announce"); + } + , [&](lt::alert const*, lt::session&) {} + ); + + // make sure there's exactly one event=completed + TEST_CHECK(std::count_if(announces.begin(), announces.end(), [](std::string const& s) + { return s.find("&event=completed") != std::string::npos; }) == 1); +} + +TORRENT_TEST(event_completed_downloading_replace_trackers) +{ + auto const announces = test_event(swarm_test::download + , [](lt::add_torrent_params& params) {} + , [&](lt::alert const* a, lt::session&) { + if (auto const* at = alert_cast(a)) + at->handle.replace_trackers({announce_entry{"http://2.2.2.2:8080/announce"}}); + } + ); + + // make sure there's exactly one event=completed + TEST_CHECK(std::count_if(announces.begin(), announces.end(), [](std::string const& s) + { return s.find("&event=completed") != std::string::npos; }) == 1); +} + +TORRENT_TEST(event_completed_seeding) +{ + auto const announces = test_event(swarm_test::upload | swarm_test::no_auto_stop + , [](lt::add_torrent_params& params) { + params.trackers.push_back("http://2.2.2.2:8080/announce"); + } + , [&](lt::alert const*, lt::session&) {} + ); + + // make sure there are no event=completed, since we added the torrent as a + // seed + TEST_CHECK(std::count_if(announces.begin(), announces.end(), [](std::string const& s) + { return s.find("&event=completed") != std::string::npos; }) == 0); +} + + +TORRENT_TEST(event_completed_seeding_replace_trackers) +{ + auto const announces = test_event(swarm_test::upload | swarm_test::no_auto_stop + , [](lt::add_torrent_params& params) {} + , [&](lt::alert const* a, lt::session&) { + if (auto const* at = alert_cast(a)) + at->handle.replace_trackers({announce_entry{"http://2.2.2.2:8080/announce"}}); + } + ); + + // make sure there are no event=completed, since we added the torrent as a + // seed + TEST_CHECK(std::count_if(announces.begin(), announces.end(), [](std::string const& s) + { return s.find("&event=completed") != std::string::npos; }) == 0); +} + +TORRENT_TEST(announce_interval_440) +{ + test_interval(440); +} + +TORRENT_TEST(announce_interval_1800) +{ + test_interval(1800); +} + +TORRENT_TEST(announce_interval_1200) +{ + test_interval(3600); +} + +struct sim_config : sim::default_config +{ + explicit sim_config(bool ipv6 = true) : ipv6(ipv6) {} + + chrono::high_resolution_clock::duration hostname_lookup( + asio::ip::address const& requestor + , std::string hostname + , std::vector& result + , boost::system::error_code& ec) override + { + if (hostname == "tracker.com") + { + result.push_back(make_address_v4("123.0.0.2")); + if (ipv6) + result.push_back(make_address_v6("ff::dead:beef")); + return duration_cast(chrono::milliseconds(100)); + } + if (hostname == "localhost") + { + result.push_back(make_address_v4("127.0.0.1")); + if (ipv6) + result.push_back(make_address_v6("::1")); + return duration_cast(chrono::milliseconds(1)); + } + if (hostname == "xn--tracker-.com") + { + result.push_back(make_address_v4("123.0.0.2")); + return duration_cast(chrono::milliseconds(100)); + } + if (hostname == "redirector.com") + { + result.push_back(make_address_v4("123.0.0.4")); + return duration_cast(chrono::milliseconds(100)); + } + + return default_config::hostname_lookup(requestor, hostname, result, ec); + } + + bool ipv6; +}; + +void on_alert_notify(lt::session* ses) +{ + post(ses->get_context(), [ses] { + std::vector alerts; + ses->pop_alerts(&alerts); + + for (lt::alert* a : alerts) + { + lt::time_duration d = a->timestamp().time_since_epoch(); + std::uint32_t const millis = std::uint32_t( + lt::duration_cast(d).count()); + std::printf("%4d.%03d: %s\n", millis / 1000, millis % 1000, + a->message().c_str()); + } + }); +} + +static const int num_interfaces = 3; + +void test_ipv6_support(char const* listen_interfaces + , int const expect_v4, int const expect_v6) +{ + using sim::asio::ip::address_v4; + sim_config network_cfg; + sim::simulation sim{network_cfg}; + + sim::asio::io_context web_server_v4(sim, make_address_v4("123.0.0.2")); + sim::asio::io_context web_server_v6(sim, make_address_v6("ff::dead:beef")); + + // listen on port 8080 + sim::http_server http_v4(web_server_v4, 8080); + sim::http_server http_v6(web_server_v6, 8080); + + int v4_announces = 0; + int v6_announces = 0; + + // if we're not listening we'll just report port 0 + std::string const expect_port = (listen_interfaces && listen_interfaces == ""_sv) + ? "&port=1" : "&port=6881"; + + http_v4.register_handler("/announce" + , [&v4_announces,expect_port](std::string method, std::string req + , std::map&) + { + ++v4_announces; + TEST_EQUAL(method, "GET"); + TEST_CHECK(req.find(expect_port) != std::string::npos); + char response[500]; + int const size = std::snprintf(response, sizeof(response), "d8:intervali1800e5:peers0:e"); + return sim::send_response(200, "OK", size) + response; + }); + + http_v6.register_handler("/announce" + , [&v6_announces,expect_port](std::string method, std::string req + , std::map&) + { + ++v6_announces; + TEST_EQUAL(method, "GET"); + + TEST_CHECK(req.find(expect_port) != std::string::npos); + char response[500]; + int const size = std::snprintf(response, sizeof(response), "d8:intervali1800e5:peers0:e"); + return sim::send_response(200, "OK", size) + response; + }); + + { + lt::session_proxy zombie; + + std::vector ips; + + for (int i = 0; i < num_interfaces; i++) + { + char ep[30]; + std::snprintf(ep, sizeof(ep), "123.0.0.%d", i + 1); + ips.push_back(make_address(ep)); + std::snprintf(ep, sizeof(ep), "ffff::1337:%d", i + 1); + ips.push_back(make_address(ep)); + } + + asio::io_context ios(sim, ips); + lt::settings_pack sett = settings(); + if (listen_interfaces) + { + sett.set_str(settings_pack::listen_interfaces, listen_interfaces); + } + auto ses = std::make_unique(sett, ios); + + ses->set_alert_notify(std::bind(&on_alert_notify, ses.get())); + + lt::add_torrent_params p; + p.name = "test-torrent"; + p.save_path = "."; + p.info_hashes.v1.assign("abababababababababab"); + +//TODO: parameterize http vs. udp here + p.trackers.push_back("http://tracker.com:8080/announce"); + ses->async_add_torrent(p); + + // stop the torrent 5 seconds in + sim::timer t1(sim, lt::seconds(5) + , [&ses](boost::system::error_code const&) + { + std::vector torrents = ses->get_torrents(); + for (auto const& t : torrents) + { + t.pause(); + } + }); + + // then shut down 10 seconds in + sim::timer t2(sim, lt::seconds(10) + , [&ses,&zombie](boost::system::error_code const&) + { + zombie = ses->abort(); + ses.reset(); + }); + + sim.run(); + } + + TEST_EQUAL(v4_announces, expect_v4); + TEST_EQUAL(v6_announces, expect_v6); +} + +void test_udpv6_support(char const* listen_interfaces + , int const expect_v4, int const expect_v6) +{ + using sim::asio::ip::address_v4; + sim_config network_cfg; + sim::simulation sim{network_cfg}; + + sim::asio::io_context web_server_v4(sim, make_address_v4("123.0.0.2")); + sim::asio::io_context web_server_v6(sim, make_address_v6("ff::dead:beef")); + + int v4_announces = 0; + int v6_announces = 0; + + { + lt::session_proxy zombie; + + std::vector ips; + + for (int i = 0; i < num_interfaces; i++) + { + char ep[30]; + std::snprintf(ep, sizeof(ep), "123.0.0.%d", i + 1); + ips.push_back(make_address(ep)); + std::snprintf(ep, sizeof(ep), "ffff::1337:%d", i + 1); + ips.push_back(make_address(ep)); + } + + asio::io_context ios(sim, ips); + lt::settings_pack sett = settings(); + if (listen_interfaces) + { + sett.set_str(settings_pack::listen_interfaces, listen_interfaces); + } + auto ses = std::make_unique(sett, ios); + + // since we don't have a udp tracker to run in the sim, looking for the + // alerts is the closest proxy + ses->set_alert_notify([&]{ + post(ses->get_context(), [&] { + std::vector alerts; + ses->pop_alerts(&alerts); + + for (lt::alert* a : alerts) + { + lt::time_duration d = a->timestamp().time_since_epoch(); + std::uint32_t const millis = std::uint32_t( + lt::duration_cast(d).count()); + std::printf("%4d.%03d: %s\n", millis / 1000, millis % 1000, + a->message().c_str()); + if (auto tr = alert_cast(a)) + { + if (lt::aux::is_v4(tr->local_endpoint)) + ++v4_announces; + else + ++v6_announces; + } + else if (alert_cast(a)) + { + TEST_ERROR("unexpected tracker error"); + } + } + }); + }); + + lt::add_torrent_params p; + p.name = "test-torrent"; + p.save_path = "."; + p.info_hashes.v1.assign("abababababababababab"); + + p.trackers.push_back("udp://tracker.com:8080/announce"); + ses->async_add_torrent(p); + + // stop the torrent 5 seconds in + sim::timer t1(sim, lt::seconds(5) + , [&ses](boost::system::error_code const&) + { + std::vector torrents = ses->get_torrents(); + for (auto const& t : torrents) + { + t.pause(); + } + }); + + // then shut down 10 seconds in + sim::timer t2(sim, lt::seconds(10) + , [&ses,&zombie](boost::system::error_code const&) + { + zombie = ses->abort(); + ses.reset(); + }); + + sim.run(); + } + + TEST_EQUAL(v4_announces, expect_v4); + TEST_EQUAL(v6_announces, expect_v6); +} + +// this test makes sure that a tracker whose host name resolves to both IPv6 and +// IPv4 addresses will be announced to twice, once for each address family +TORRENT_TEST(ipv6_support) +{ + // null means default + test_ipv6_support(nullptr, num_interfaces * 2, num_interfaces * 2); +} + +TORRENT_TEST(announce_no_listen) +{ + // if we don't listen on any sockets at all we should not announce to trackers + test_ipv6_support("", 0, 0); +} + +TORRENT_TEST(announce_udp_no_listen) +{ + // if we don't listen on any sockets at all we should not announce to trackers + test_udpv6_support("", 0, 0); +} + +TORRENT_TEST(ipv6_support_bind_v4_v6_any) +{ + // 2 because there's one announce on startup and one when shutting down + // IPv6 will send announces for each interface + test_ipv6_support("0.0.0.0:6881,[::0]:6881", num_interfaces * 2, num_interfaces * 2); +} + +TORRENT_TEST(ipv6_support_bind_v6_any) +{ + test_ipv6_support("[::0]:6881", 0, num_interfaces * 2); +} + +TORRENT_TEST(ipv6_support_bind_v4) +{ + test_ipv6_support("123.0.0.3:6881", 2, 0); +} + +TORRENT_TEST(ipv6_support_bind_v6) +{ + test_ipv6_support("[ffff::1337:1]:6881", 0, 2); +} + +TORRENT_TEST(ipv6_support_bind_v6_3interfaces) +{ + test_ipv6_support("[ffff::1337:1]:6881,[ffff::1337:2]:6881,[ffff::1337:3]:6881", 0, 3 * 2); +} + +TORRENT_TEST(ipv6_support_bind_v4_v6) +{ + test_ipv6_support("123.0.0.3:6881,[ffff::1337:1]:6881", 2, 2); +} + +TORRENT_TEST(ipv6_support_bind_v6_v4) +{ + test_ipv6_support("[ffff::1337:1]:6881,123.0.0.3:6881", 2, 2); +} + +// this runs a simulation of a torrent with tracker(s), making sure the request +// received by the tracker matches the expectation. +// The Setup function is run first, giving the test an opportunity to add +// trackers to the torrent. It's expected to return the number of seconds to +// wait until test2 is called. +// The Announce function is called on http requests. Test1 is run on the session +// 5 seconds after startup. The tracker is running at 123.0.0.2 (or tracker.com) +// port 8080. +template +void tracker_test(Setup setup, Announce a, Test1 test1, Test2 test2 + , char const* url_path = "/announce" + , char const* redirect = "http://123.0.0.2/announce") +{ + using sim::asio::ip::address_v4; + sim_config network_cfg; + sim::simulation sim{network_cfg}; + + sim::asio::io_context tracker_ios(sim, make_address_v4("123.0.0.2")); + sim::asio::io_context tracker_ios6(sim, make_address_v6("ff::dead:beef")); + sim::asio::io_context redirector_ios(sim, make_address_v4("123.0.0.4")); + + sim::asio::io_context tracker_lo_ios(sim, make_address_v4("127.0.0.1")); + sim::asio::io_context tracker_lo_ios6(sim, make_address_v6("::1")); + + // listen on port 8080 + sim::http_server http(tracker_ios, 8080); + sim::http_server http6(tracker_ios6, 8080); + sim::http_server http_lo(tracker_lo_ios, 8080); + sim::http_server http6_lo(tracker_lo_ios6, 8080); + sim::http_server http_redirect(redirector_ios, 8080); + + http.register_handler(url_path, a); + http6.register_handler(url_path, a); + http_lo.register_handler(url_path, a); + http6_lo.register_handler(url_path, a); + http_redirect.register_redirect(url_path, redirect); + + lt::session_proxy zombie; + + asio::io_context ios(sim, { make_address_v4("123.0.0.3") + , make_address_v6("ffff::1337") }); + lt::settings_pack sett = settings(); + auto ses = std::make_unique(sett, ios); + + ses->set_alert_notify(std::bind(&on_alert_notify, ses.get())); + + lt::add_torrent_params p; + p.name = "test-torrent"; + p.save_path = "."; + p.info_hashes.v1.assign("abababababababababab"); + int const delay = setup(p, *ses); + ses->async_add_torrent(p); + + // run the test 5 seconds in + sim::timer t1(sim, lt::seconds(5) + , [&ses,&test1](boost::system::error_code const&) + { + std::vector torrents = ses->get_torrents(); + TEST_EQUAL(torrents.size(), 1); + torrent_handle h = torrents.front(); + test1(h); + }); + + sim::timer t2(sim, lt::seconds(5 + delay) + , [&ses,&test2](boost::system::error_code const&) + { + std::vector torrents = ses->get_torrents(); + TEST_EQUAL(torrents.size(), 1); + torrent_handle h = torrents.front(); + test2(h); + }); + + // then shut down 10 seconds in + sim::timer t3(sim, lt::seconds(10 + delay) + , [&ses,&zombie](boost::system::error_code const&) + { + zombie = ses->abort(); + ses.reset(); + }); + + sim.run(); +} + +template +void tracker_test(Announce a, Test1 test1, Test2 test2, char const* url_path = "/announce") +{ + tracker_test([](lt::add_torrent_params& p, lt::session&) { + p.trackers.push_back("http://tracker.com:8080/announce"); + return 5; + }, + a, test1, test2, url_path); +} + +template +void announce_entry_test(Announce a, Test t, char const* url_path = "/announce") +{ + tracker_test(a + , [&t] (torrent_handle h) { + std::vector tr = h.trackers(); + + TEST_EQUAL(tr.size(), 1); + announce_entry const& ae = tr[0]; + t(ae); + } + , [](torrent_handle){} + , url_path); +} + +// test that we correctly omit announcing an event=stopped to a tracker we never +// managed to send an event=start to +TORRENT_TEST(omit_stop_event) +{ + using sim::asio::ip::address_v4; + sim_config network_cfg; + sim::simulation sim{network_cfg}; + + lt::session_proxy zombie; + + asio::io_context ios(sim, { make_address_v4("123.0.0.3"), make_address_v6("ff::dead:beef")}); + lt::settings_pack sett = settings(); + std::unique_ptr ses(new lt::session(sett, ios)); + + print_alerts(*ses); + + lt::add_torrent_params p; + p.name = "test-torrent"; + p.save_path = "."; + p.info_hashes.v1.assign("abababababababababab"); + p.trackers.push_back("udp://tracker.com:8080/announce"); + ses->async_add_torrent(p); + + // run the test 5 seconds in + sim::timer t1(sim, lt::seconds(5) + , [&ses](boost::system::error_code const&) + { + std::vector torrents = ses->get_torrents(); + TEST_EQUAL(torrents.size(), 1); + torrent_handle h = torrents.front(); + }); + + int stop_announces = 0; + + sim::timer t2(sim, lt::seconds(1800) + , [&](boost::system::error_code const&) + { + // make sure we don't announce a stopped event when stopping + print_alerts(*ses, [&](lt::session&, lt::alert const* a) { + if (alert_cast(a)) + ++stop_announces; + }); + std::vector torrents = ses->get_torrents(); + TEST_EQUAL(torrents.size(), 1); + torrent_handle h = torrents.front(); + h.set_flags(torrent_flags::paused, torrent_flags::paused | torrent_flags::auto_managed); + }); + + // then shut down 10 seconds in + sim::timer t3(sim, lt::seconds(1810) + , [&](boost::system::error_code const&) + { + zombie = ses->abort(); + ses.reset(); + }); + + sim.run(); + + TEST_EQUAL(stop_announces, 0); +} + +TORRENT_TEST(test_error) +{ + announce_entry_test( + [](std::string method, std::string req + , std::map&) + { + TEST_EQUAL(method, "GET"); + + char response[500]; + int const size = std::snprintf(response, sizeof(response), "d14:failure reason4:teste"); + return sim::send_response(200, "OK", size) + response; + } + , [](announce_entry const& ae) + { + TEST_EQUAL(ae.url, "http://tracker.com:8080/announce"); + TEST_EQUAL(ae.endpoints.size(), 2); + for (auto const& aep : ae.endpoints) + { + TEST_EQUAL(aep.info_hashes[protocol_version::V1].message, "test"); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].last_error, error_code(errors::tracker_failure)); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].fails, 1); + } + }); +} + +TORRENT_TEST(test_no_announce_path) +{ + tracker_test( + [](lt::add_torrent_params& p, lt::session&) { + p.trackers.push_back("http://tracker.com:8080"); + return 5; + }, + [](std::string method, std::string req, std::map&) + { + TEST_EQUAL(method, "GET"); + + char response[500]; + int const size = std::snprintf(response, sizeof(response), "d5:peers6:aaaaaae"); + return sim::send_response(200, "OK", size) + response; + } + , [](torrent_handle h) + { + std::vector tr = h.trackers(); + + TEST_EQUAL(tr.size(), 1); + announce_entry const& ae = tr[0]; + TEST_EQUAL(ae.url, "http://tracker.com:8080"); + TEST_EQUAL(ae.endpoints.size(), 2); + for (auto const& aep : ae.endpoints) + { + TEST_EQUAL(aep.info_hashes[protocol_version::V1].message, ""); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].last_error, error_code()); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].fails, 0); + } + } + , [](torrent_handle){} + , "/"); +} + +TORRENT_TEST(test_warning) +{ + announce_entry_test( + [](std::string method, std::string req + , std::map&) + { + TEST_EQUAL(method, "GET"); + + char response[500]; + int const size = std::snprintf(response, sizeof(response), "d5:peers6:aaaaaa15:warning message5:test2e"); + return sim::send_response(200, "OK", size) + response; + } + , [](announce_entry const& ae) + { + TEST_EQUAL(ae.url, "http://tracker.com:8080/announce"); + TEST_EQUAL(ae.endpoints.size(), 2); + for (auto const& aep : ae.endpoints) + { + TEST_EQUAL(aep.info_hashes[protocol_version::V1].message, "test2"); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].last_error, error_code()); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].fails, 0); + } + }); +} + +TORRENT_TEST(test_scrape_data_in_announce) +{ + announce_entry_test( + [](std::string method, std::string req + , std::map&) + { + TEST_EQUAL(method, "GET"); + + char response[500]; + int const size = std::snprintf(response, sizeof(response), + "d5:peers6:aaaaaa8:completei1e10:incompletei2e10:downloadedi3e11:downloadersi4ee"); + return sim::send_response(200, "OK", size) + response; + } + , [](announce_entry const& ae) + { + TEST_EQUAL(ae.url, "http://tracker.com:8080/announce"); + TEST_EQUAL(ae.endpoints.size(), 2); + for (auto const& aep : ae.endpoints) + { + TEST_EQUAL(aep.info_hashes[protocol_version::V1].message, ""); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].last_error, error_code()); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].fails, 0); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].scrape_complete, 1); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].scrape_incomplete, 2); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].scrape_downloaded, 3); + } + }); +} + +TORRENT_TEST(test_scrape) +{ + tracker_test( + [](std::string method, std::string req + , std::map&) + { + TEST_EQUAL(method, "GET"); + + char response[500]; + int const size = std::snprintf(response, sizeof(response), + "d5:filesd20:ababababababababababd8:completei1e10:downloadedi3e10:incompletei2eeee"); + return sim::send_response(200, "OK", size) + response; + } + , [](torrent_handle h) + { + h.scrape_tracker(); + } + , [](torrent_handle h) + { + std::vector tr = h.trackers(); + + TEST_EQUAL(tr.size(), 1); + announce_entry const& ae = tr[0]; + TEST_EQUAL(ae.endpoints.size(), 2); + for (auto const& aep : ae.endpoints) + { + TEST_EQUAL(aep.info_hashes[protocol_version::V1].scrape_incomplete, 2); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].scrape_complete, 1); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].scrape_downloaded, 3); + } + } + , "/scrape"); +} + +TORRENT_TEST(test_http_status) +{ + announce_entry_test( + [](std::string method, std::string req + , std::map&) + { + TEST_EQUAL(method, "GET"); + return sim::send_response(410, "Not A Tracker", 0); + } + , [](announce_entry const& ae) + { + TEST_EQUAL(ae.url, "http://tracker.com:8080/announce"); + TEST_EQUAL(ae.endpoints.size(), 2); + for (auto const& aep : ae.endpoints) + { + TEST_EQUAL(aep.info_hashes[protocol_version::V1].message, "Not A Tracker"); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].last_error, error_code(410, http_category())); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].fails, 1); + } + }); +} + +TORRENT_TEST(test_interval) +{ + announce_entry_test( + [](std::string method, std::string req + , std::map&) + { + TEST_EQUAL(method, "GET"); + char response[500]; + int const size = std::snprintf(response, sizeof(response) + , "d10:tracker id8:testteste"); + return sim::send_response(200, "OK", size) + response; + } + , [](announce_entry const& ae) + { + TEST_EQUAL(ae.url, "http://tracker.com:8080/announce"); + TEST_EQUAL(ae.endpoints.size(), 2); + for (auto const& aep : ae.endpoints) + { + TEST_EQUAL(aep.info_hashes[protocol_version::V1].message, ""); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].last_error, error_code()); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].fails, 0); + } + + TEST_EQUAL(ae.trackerid, "testtest"); + }); +} + +TORRENT_TEST(test_invalid_bencoding) +{ + announce_entry_test( + [](std::string method, std::string req + , std::map&) + { + TEST_EQUAL(method, "GET"); + char response[500]; + int const size = std::snprintf(response, sizeof(response) + , "d10:tracer idteste"); + return sim::send_response(200, "OK", size) + response; + } + , [](announce_entry const& ae) + { + TEST_EQUAL(ae.url, "http://tracker.com:8080/announce"); + TEST_EQUAL(ae.endpoints.size(), 2); + for (auto const& aep : ae.endpoints) + { + TEST_EQUAL(aep.info_hashes[protocol_version::V1].message, ""); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].last_error, error_code(bdecode_errors::expected_value + , bdecode_category())); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].fails, 1); + } + }); +} + +TORRENT_TEST(try_next) +{ +// test that we move on to try the next tier if the first one fails + + bool got_announce = false; + tracker_test( + [](lt::add_torrent_params& p, lt::session&) + { + // TODO: 3 use tracker_tiers here to put the trackers in different tiers + p.trackers.push_back("udp://failing-tracker.com/announce"); + p.trackers.push_back("http://failing-tracker.com/announce"); + + // this is the working tracker + p.trackers.push_back("http://tracker.com:8080/announce"); + return 60; + }, + [&](std::string method, std::string req + , std::map&) + { + got_announce = true; + TEST_EQUAL(method, "GET"); + + char response[500]; + // respond with an empty peer list + int const size = std::snprintf(response, sizeof(response), "d5:peers0:e"); + return sim::send_response(200, "OK", size) + response; + } + , [](torrent_handle h) {} + , [](torrent_handle h) + { + torrent_status st = h.status(); + TEST_EQUAL(st.current_tracker, "http://tracker.com:8080/announce"); + + std::vector tr = h.trackers(); + + TEST_EQUAL(tr.size(), 3); + + for (int i = 0; i < int(tr.size()); ++i) + { + std::printf("tracker \"%s\"\n", tr[i].url.c_str()); + if (tr[i].url == "http://tracker.com:8080/announce") + { + for (auto const& aep : tr[i].endpoints) + { + TEST_EQUAL(aep.info_hashes[protocol_version::V1].fails, 0); + } + TEST_EQUAL(tr[i].verified, true); + } + else if (tr[i].url == "http://failing-tracker.com/announce") + { + for (auto const& aep : tr[i].endpoints) + { + TEST_CHECK(aep.info_hashes[protocol_version::V1].fails >= 1); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].last_error + , error_code(boost::asio::error::host_not_found)); + } + TEST_EQUAL(tr[i].verified, false); + } + else if (tr[i].url == "udp://failing-tracker.com/announce") + { + TEST_EQUAL(tr[i].verified, false); + for (auto const& aep : tr[i].endpoints) + { + TEST_CHECK(aep.info_hashes[protocol_version::V1].fails >= 1); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].last_error + , error_code(boost::asio::error::host_not_found)); + } + } + else + { + TEST_ERROR(("unexpected tracker URL: " + tr[i].url).c_str()); + } + } + }); + TEST_EQUAL(got_announce, true); +} + +TORRENT_TEST(clear_error) +{ + // make sure we clear the error from a previous attempt when succeeding + // a tracker announce + + int num_announces = 0; + tracker_test( + [](lt::add_torrent_params& p, lt::session& ses) + { + settings_pack pack; + // make sure we just listen on a single listen interface + pack.set_str(settings_pack::listen_interfaces, "123.0.0.3:0"); + pack.set_int(settings_pack::min_announce_interval, 1); + pack.set_int(settings_pack::tracker_backoff, 1); + ses.apply_settings(pack); + p.trackers.push_back("http://tracker.com:8080/announce"); + return 60; + }, + [&](std::string method, std::string req, std::map&) + { + // don't count the stopped event when shutting down + if (req.find("&event=stopped&") != std::string::npos) + { + return sim::send_response(200, "OK", 2) + "de"; + } + if (num_announces++ == 0) + { + // the first announce fails + return std::string{}; + } + + // the second announce succeeds, with an empty peer list + char response[500]; + int const size = std::snprintf(response, sizeof(response), "d8:intervali1800e5:peers0:e"); + return sim::send_response(200, "OK", size) + response; + } + , [](torrent_handle h) { + + } + , [&](torrent_handle h) + { + std::vector const tr = h.trackers(); + TEST_EQUAL(tr.size(), 1); + + std::printf("tracker \"%s\"\n", tr[0].url.c_str()); + TEST_EQUAL(tr[0].url, "http://tracker.com:8080/announce"); + TEST_EQUAL(tr[0].endpoints.size(), 1); + auto const& aep = tr[0].endpoints[0]; + + TEST_EQUAL(aep.info_hashes[protocol_version::V1].fails, 0); + TEST_CHECK(!aep.info_hashes[protocol_version::V1].last_error); + TEST_EQUAL(aep.info_hashes[protocol_version::V1].message, ""); + +#if TORRENT_ABI_VERSION <= 2 + TEST_EQUAL(aep.fails, 0); + TEST_CHECK(!aep.last_error); + TEST_EQUAL(aep.message, ""); +#endif + }); + TEST_EQUAL(num_announces, 2); +} + +std::shared_ptr make_torrent(bool priv) +{ + file_storage fs; + fs.add_file("foobar", 13241); + lt::create_torrent ct(fs); + + ct.add_tracker("http://tracker.com:8080/announce"); + + for (piece_index_t i(0); i < piece_index_t(ct.num_pieces()); ++i) + ct.set_hash(i, sha1_hash::max()); + + ct.set_priv(priv); + + entry e = ct.generate(); + std::vector buf; + bencode(std::back_inserter(buf), e); + return std::make_shared(buf, from_span); +} + +// make sure we _do_ send our IPv6 address to trackers for private torrents +TORRENT_TEST(tracker_ipv6_argument) +{ + bool got_announce = false; + bool got_ipv6 = false; + bool got_ipv4 = false; + tracker_test( + [](lt::add_torrent_params& p, lt::session& ses) + { + settings_pack pack; + pack.set_bool(settings_pack::anonymous_mode, false); + pack.set_str(settings_pack::listen_interfaces, "123.0.0.3:0,[ffff::1337]:0"); + ses.apply_settings(pack); + p.ti = make_torrent(true); + p.info_hashes = lt::info_hash_t{}; + return 60; + }, + [&](std::string method, std::string req + , std::map&) + { + got_announce = true; + bool const stop_event = req.find("&event=stopped") != std::string::npos; + // stop events don't need to advertise the IPv6/IPv4 address + { + std::string::size_type const pos = req.find("&ipv6="); + TEST_CHECK(pos != std::string::npos || stop_event); + got_ipv6 |= pos != std::string::npos; + // make sure the IPv6 argument is url encoded + TEST_EQUAL(req.substr(pos + 6, req.substr(pos + 6).find_first_of('&')) + , "ffff%3a%3a1337"); + } + + { + std::string::size_type const pos = req.find("&ipv4="); + TEST_CHECK(pos != std::string::npos || stop_event); + got_ipv4 |= pos != std::string::npos; + TEST_EQUAL(req.substr(pos + 6, req.substr(pos + 6).find_first_of('&')), "123.0.0.3"); + } + return sim::send_response(200, "OK", 11) + "d5:peers0:e"; + } + , [](torrent_handle) {} + , [](torrent_handle) {}); + TEST_EQUAL(got_announce, true); + TEST_EQUAL(got_ipv6, true); +} + +TORRENT_TEST(tracker_key_argument) +{ + std::set keys; + tracker_test( + [](lt::add_torrent_params& p, lt::session&) + { + p.ti = make_torrent(true); + p.info_hashes = lt::info_hash_t{}; + return 60; + }, + [&](std::string, std::string req + , std::map&) + { + auto const pos = req.find("&key="); + TEST_CHECK(pos != std::string::npos); + keys.insert(req.substr(pos + 5, req.find_first_of('&', pos + 5) - pos - 5)); + return sim::send_response(200, "OK", 11) + "d5:peers0:e"; + } + , [](torrent_handle h) {} + , [](torrent_handle h) {}); + + // make sure we got the same key for all listen socket interface + TEST_EQUAL(keys.size(), 1); +} + +// make sure we do _not_ send our IPv6 address to trackers for non-private +// torrents +TORRENT_TEST(tracker_ipv6_argument_non_private) +{ + bool got_announce = false; + bool got_ipv6 = false; + tracker_test( + [](lt::add_torrent_params& p, lt::session& ses) + { + settings_pack pack; + pack.set_bool(settings_pack::anonymous_mode, false); + ses.apply_settings(pack); + p.ti = make_torrent(false); + p.info_hashes = lt::info_hash_t{}; + return 60; + }, + [&](std::string method, std::string req + , std::map&) + { + got_announce = true; + std::string::size_type pos = req.find("&ipv6="); + TEST_CHECK(pos == std::string::npos); + got_ipv6 |= pos != std::string::npos; + return sim::send_response(200, "OK", 11) + "d5:peers0:e"; + } + , [](torrent_handle) {} + , [](torrent_handle) {}); + TEST_EQUAL(got_announce, true); + TEST_EQUAL(got_ipv6, false); +} + +TORRENT_TEST(tracker_ipv6_argument_privacy_mode) +{ + bool got_announce = false; + bool got_ipv6 = false; + tracker_test( + [](lt::add_torrent_params& p, lt::session& ses) + { + settings_pack pack; + pack.set_bool(settings_pack::anonymous_mode, true); + ses.apply_settings(pack); + p.ti = make_torrent(true); + p.info_hashes = lt::info_hash_t{}; + return 60; + }, + [&](std::string method, std::string req + , std::map&) + { + got_announce = true; + std::string::size_type pos = req.find("&ipv6="); + TEST_CHECK(pos == std::string::npos); + got_ipv6 |= pos != std::string::npos; + return sim::send_response(200, "OK", 11) + "d5:peers0:e"; + } + , [](torrent_handle) {} + , [](torrent_handle) {}); + TEST_EQUAL(got_announce, true); + TEST_EQUAL(got_ipv6, false); +} + +TORRENT_TEST(tracker_user_agent_privacy_mode_public_torrent) +{ + bool got_announce = false; + tracker_test( + [](lt::add_torrent_params& p, lt::session& ses) + { + settings_pack pack; + pack.set_bool(settings_pack::anonymous_mode, true); + pack.set_str(settings_pack::user_agent, "test_agent/1.2.3"); + ses.apply_settings(pack); + p.ti = make_torrent(false); + p.info_hashes = lt::info_hash_t{}; + return 60; + }, + [&](std::string method, std::string req + , std::map& headers) + { + got_announce = true; + + // in anonymous mode we should send a generic user agent + TEST_CHECK(headers["user-agent"] == "curl/7.81.0"); + return sim::send_response(200, "OK", 11) + "d5:peers0:e"; + } + , [](torrent_handle h) {} + , [](torrent_handle h) {}); + TEST_EQUAL(got_announce, true); +} + +TORRENT_TEST(tracker_user_agent_privacy_mode_private_torrent) +{ + bool got_announce = false; + tracker_test( + [](lt::add_torrent_params& p, lt::session& ses) + { + settings_pack pack; + pack.set_bool(settings_pack::anonymous_mode, true); + pack.set_str(settings_pack::user_agent, "test_agent/1.2.3"); + ses.apply_settings(pack); + p.ti = make_torrent(true); + p.info_hashes = lt::info_hash_t{}; + return 60; + }, + [&](std::string method, std::string req + , std::map& headers) + { + got_announce = true; + + // in anonymous mode we should still send the user agent for private + // torrents (since private trackers sometimes require it) + TEST_CHECK(headers["user-agent"] == "test_agent/1.2.3"); + return sim::send_response(200, "OK", 11) + "d5:peers0:e"; + } + , [](torrent_handle h) {} + , [](torrent_handle h) {}); + TEST_EQUAL(got_announce, true); +} + +bool test_ssrf(char const* announce_path, bool const feature_on + , char const* tracker_url) +{ + bool got_announce = false; + tracker_test( + [&](lt::add_torrent_params& p, lt::session& ses) + { + settings_pack pack; + pack.set_bool(settings_pack::ssrf_mitigation, feature_on); + ses.apply_settings(pack); + p.trackers.emplace_back(tracker_url); + return 60; + }, + [&](std::string method, std::string req + , std::map& headers) + { + got_announce = true; + return sim::send_response(200, "OK", 11) + "d5:peers0:e"; + } + , [](torrent_handle h) {} + , [](torrent_handle h) {} + , announce_path); + return got_announce; +} + +TORRENT_TEST(ssrf_localhost) +{ + TEST_CHECK(test_ssrf("/announce", true, "http://localhost:8080/announce")); + TEST_CHECK(!test_ssrf("/unusual-announce-path", true, "http://localhost:8080/unusual-announce-path")); + TEST_CHECK(test_ssrf("/unusual-announce-path", false, "http://localhost:8080/unusual-announce-path")); + + TEST_CHECK(!test_ssrf("/short", true, "http://localhost:8080/short")); + TEST_CHECK(test_ssrf("/short", false, "http://localhost:8080/short")); +} + +TORRENT_TEST(ssrf_IPv4) +{ + TEST_CHECK(test_ssrf("/announce", true, "http://127.0.0.1:8080/announce")); + TEST_CHECK(!test_ssrf("/unusual-announce-path", true, "http://127.0.0.1:8080/unusual-announce-path")); + TEST_CHECK(test_ssrf("/unusual-announce-path", false, "http://127.0.0.1:8080/unusual-announce-path")); +} + +TORRENT_TEST(ssrf_IPv6) +{ + TEST_CHECK(test_ssrf("/announce", true, "http://[::1]:8080/announce")); + TEST_CHECK(!test_ssrf("/unusual-announce-path", true, "http://[::1]:8080/unusual-announce-path")); + TEST_CHECK(test_ssrf("/unusual-announce-path", false, "http://[::1]:8080/unusual-announce-path")); +} + +TORRENT_TEST(ssrf_query_string) +{ + // tracker URLs that come pre-baked with query string arguments will be + // rejected when SSRF-mitigation is enabled + TEST_CHECK(!test_ssrf("/announce", true, "http://tracker.com:8080/announce?info_hash=abc")); + TEST_CHECK(!test_ssrf("/announce", true, "http://tracker.com:8080/announce?iNfo_HaSh=abc")); + TEST_CHECK(!test_ssrf("/announce", true, "http://tracker.com:8080/announce?event=abc")); + TEST_CHECK(!test_ssrf("/announce", true, "http://tracker.com:8080/announce?EvEnT=abc")); + + TEST_CHECK(test_ssrf("/announce", false, "http://tracker.com:8080/announce?info_hash=abc")); + TEST_CHECK(test_ssrf("/announce", false, "http://tracker.com:8080/announce?iNfo_HaSh=abc")); + TEST_CHECK(test_ssrf("/announce", false, "http://tracker.com:8080/announce?event=abc")); +} + +bool test_idna(char const* tracker_url, char const* redirect + , bool const feature_on) +{ + bool got_announce = false; + tracker_test( + [&](lt::add_torrent_params& p, lt::session& ses) + { + settings_pack pack; + pack.set_bool(settings_pack::allow_idna, feature_on); + ses.apply_settings(pack); + p.trackers.emplace_back(tracker_url); + return 60; + }, + [&](std::string method, std::string req + , std::map& headers) + { + got_announce = true; + return sim::send_response(200, "OK", 11) + "d5:peers0:e"; + } + , [](torrent_handle h) {} + , [](torrent_handle h) {} + , "/announce" + , redirect ? redirect : "" + ); + return got_announce; +} + +TORRENT_TEST(tracker_idna) +{ + TEST_EQUAL(test_idna("http://tracker.com:8080/announce", nullptr, true), true); + TEST_EQUAL(test_idna("http://tracker.com:8080/announce", nullptr, false), true); + + TEST_EQUAL(test_idna("http://xn--tracker-.com:8080/announce", nullptr, true), true); + TEST_EQUAL(test_idna("http://xn--tracker-.com:8080/announce", nullptr, false), false); +} + +TORRENT_TEST(tracker_idna_redirect) +{ + TEST_EQUAL(test_idna("http://redirector.com:8080/announce", "http://xn--tracker-.com:8080/announce", true), true); + TEST_EQUAL(test_idna("http://redirector.com:8080/announce", "http://xn--tracker-.com:8080/announce", false), false); +} + +// This test sets up two peers, one seed an one downloader. The downloader has +// two trackers, both in tier 0. The behavior we expect is that it picks one of +// the trackers at random and announces to it. Since both trackers are working, +// it should not announce to the tracker it did not initially pick. + +struct tracker_ent +{ + std::string url; + int tier; +}; + +template +void test_tracker_tiers(lt::settings_pack pack + , std::vector
    local_addresses + , std::vector trackers + , TestFun test) +{ + using namespace libtorrent; + + pack.set_int(settings_pack::alert_mask, alert_category::error + | alert_category::status + | alert_category::torrent_log); + + // setup the simulation + struct sim_config : sim::default_config + { + chrono::high_resolution_clock::duration hostname_lookup( + asio::ip::address const& requestor + , std::string hostname + , std::vector& result + , boost::system::error_code& ec) + { + if (hostname == "ipv6-only-tracker.com") + { + result.push_back(addr("f8e0::1")); + } + else if (hostname == "ipv4-only-tracker.com") + { + result.push_back(addr("3.0.0.1")); + } + else if (hostname == "dual-tracker.com") + { + result.push_back(addr("f8e0::2")); + result.push_back(addr("3.0.0.2")); + } + else return default_config::hostname_lookup(requestor, hostname, result, ec); + + return lt::duration_cast(chrono::milliseconds(100)); + } + }; + + sim_config network_cfg; + sim::simulation sim{network_cfg}; + sim::asio::io_context ios0 { sim, local_addresses}; + + sim::asio::io_context tracker1(sim, addr("3.0.0.1")); + sim::asio::io_context tracker2(sim, addr("3.0.0.2")); + sim::asio::io_context tracker3(sim, addr("3.0.0.3")); + sim::asio::io_context tracker4(sim, addr("3.0.0.4")); + sim::asio::io_context tracker5(sim, addr("f8e0::1")); + sim::asio::io_context tracker6(sim, addr("f8e0::2")); + sim::http_server http1(tracker1, 8080); + sim::http_server http2(tracker2, 8080); + sim::http_server http3(tracker3, 8080); + sim::http_server http4(tracker4, 8080); + sim::http_server http5(tracker5, 8080); + sim::http_server http6(tracker6, 8080); + + int received_announce[6] = {0, 0, 0, 0, 0, 0}; + + auto const return_no_peers = [&](std::string method, std::string req + , std::map&, int const tracker_index) + { + ++received_announce[tracker_index]; + std::string const ret = "d8:intervali60e5:peers0:e"; + return sim::send_response(200, "OK", static_cast(ret.size())) + ret; + }; + + using namespace std::placeholders; + http1.register_handler("/announce", std::bind(return_no_peers, _1, _2, _3, 0)); + http2.register_handler("/announce", std::bind(return_no_peers, _1, _2, _3, 1)); + http3.register_handler("/announce", std::bind(return_no_peers, _1, _2, _3, 2)); + http4.register_handler("/announce", std::bind(return_no_peers, _1, _2, _3, 3)); + http5.register_handler("/announce", std::bind(return_no_peers, _1, _2, _3, 4)); + http6.register_handler("/announce", std::bind(return_no_peers, _1, _2, _3, 5)); + + lt::session_proxy zombie; + + // create session + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:6881,[::]:6881"); + auto ses = std::make_shared(pack, ios0); + + // only monitor alerts for session 0 (the downloader) + print_alerts(*ses); + + // the first peer is a downloader, the second peer is a seed + lt::add_torrent_params params = ::create_torrent(1); + params.flags &= ~lt::torrent_flags::auto_managed; + params.flags &= ~lt::torrent_flags::paused; + + for (auto const& t : trackers) + params.ti->add_tracker("http://" + t.url + ":8080/announce", t.tier); + + params.save_path = save_path(0); + ses->async_add_torrent(params); + + + sim::timer t(sim, lt::seconds(30), [&](boost::system::error_code const&) + { + test(received_announce); + + zombie = ses->abort(); + ses.reset(); + }); + + sim.run(); +} + +bool one_of(int a, int b) +{ + return (a == 2 && b == 0) || (a == 0 && b == 2); +} + +// the torrent is a hybrid v1 and v2 torrent, so there is one announce per +// info-hash +TORRENT_TEST(tracker_tiers_multi_homed) +{ + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_bool(settings_pack::announce_to_all_trackers, false); + test_tracker_tiers(pack, { addr("50.0.0.1"), addr("f8e0::10") } + , { {"3.0.0.1", 0}, {"3.0.0.2", 0}, {"3.0.0.3", 1}, {"3.0.0.4", 1}} + , [](int (&a)[6]) { + TEST_CHECK(one_of(a[0], a[1])); + TEST_EQUAL(a[2], 0); + TEST_EQUAL(a[3], 0); + TEST_EQUAL(a[4], 0); + TEST_EQUAL(a[5], 0); + }); +} + +TORRENT_TEST(tracker_tiers_all_trackers_multi_homed) +{ + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + test_tracker_tiers(pack, { addr("50.0.0.1"), addr("f8e0::10") } + , { {"3.0.0.1", 0}, {"3.0.0.2", 0}, {"3.0.0.3", 1}, {"3.0.0.4", 1}} + , [](int (&a)[6]) { + TEST_EQUAL(a[0], 2); + TEST_EQUAL(a[1], 2); + TEST_EQUAL(a[2], 0); + TEST_EQUAL(a[3], 0); + TEST_EQUAL(a[4], 0); + TEST_EQUAL(a[5], 0); + }); +} + +TORRENT_TEST(tracker_tiers_all_tiers_multi_homed) +{ + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_tiers, true); + pack.set_bool(settings_pack::announce_to_all_trackers, false); + test_tracker_tiers(pack, { addr("50.0.0.1"), addr("f8e0::10") } + , { {"3.0.0.1", 0}, {"3.0.0.2", 0}, {"3.0.0.3", 1}, {"3.0.0.4", 1}} + , [](int (&a)[6]) { + TEST_CHECK(one_of(a[0], a[1])); + TEST_CHECK(one_of(a[2], a[3])); + TEST_EQUAL(a[4], 0); + TEST_EQUAL(a[5], 0); + }); +} +TORRENT_TEST(tracker_tiers_all_trackers_and_tiers_multi_homed) +{ + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_tiers, true); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + test_tracker_tiers(pack, { addr("50.0.0.1"), addr("f8e0::10") } + , { {"3.0.0.1", 0}, {"3.0.0.2", 0}, {"3.0.0.3", 1}, {"3.0.0.4", 1}} + , [](int (&a)[6]) { + TEST_EQUAL(a[0], 2); + TEST_EQUAL(a[1], 2); + TEST_EQUAL(a[2], 2); + TEST_EQUAL(a[3], 2); + TEST_EQUAL(a[4], 0); + TEST_EQUAL(a[5], 0); + }); +} + +TORRENT_TEST(tracker_tiers) +{ + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_bool(settings_pack::announce_to_all_trackers, false); + test_tracker_tiers(pack, { addr("50.0.0.1") } + , { {"3.0.0.1", 0}, {"3.0.0.2", 0}, {"3.0.0.3", 1}, {"3.0.0.4", 1}} + , [](int (&a)[6]) { + TEST_CHECK(one_of(a[0], a[1])); + TEST_EQUAL(a[2], 0); + TEST_EQUAL(a[3], 0); + TEST_EQUAL(a[4], 0); + TEST_EQUAL(a[5], 0); + }); +} + +TORRENT_TEST(tracker_tiers_all_trackers) +{ + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + test_tracker_tiers(pack, { addr("50.0.0.1") } + , { {"3.0.0.1", 0}, {"3.0.0.2", 0}, {"3.0.0.3", 1}, {"3.0.0.4", 1}} + , [](int (&a)[6]) { + TEST_EQUAL(a[0], 2); + TEST_EQUAL(a[1], 2); + TEST_EQUAL(a[2], 0); + TEST_EQUAL(a[3], 0); + TEST_EQUAL(a[4], 0); + TEST_EQUAL(a[5], 0); + }); +} + +TORRENT_TEST(tracker_tiers_all_tiers) +{ + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_tiers, true); + pack.set_bool(settings_pack::announce_to_all_trackers, false); + test_tracker_tiers(pack, { addr("50.0.0.1") } + , { {"3.0.0.1", 0}, {"3.0.0.2", 0}, {"3.0.0.3", 1}, {"3.0.0.4", 1}} + , [](int (&a)[6]) { + TEST_CHECK(one_of(a[0], a[1])); + TEST_CHECK(one_of(a[2], a[3])); + TEST_EQUAL(a[4], 0); + TEST_EQUAL(a[5], 0); + }); +} + +TORRENT_TEST(tracker_tiers_all_trackers_and_tiers) +{ + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_tiers, true); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + test_tracker_tiers(pack, { addr("50.0.0.1") } + , { {"3.0.0.1", 0}, {"3.0.0.2", 0}, {"3.0.0.3", 1}, {"3.0.0.4", 1}} + , [](int (&a)[6]) { + TEST_EQUAL(a[0], 2); + TEST_EQUAL(a[1], 2); + TEST_EQUAL(a[2], 2); + TEST_EQUAL(a[3], 2); + TEST_EQUAL(a[4], 0); + TEST_EQUAL(a[5], 0); + }); +} + +// in this case, we only have an IPv4 address, and the first tracker resolves +// only to an IPv6 address. Make sure we move on to the next one in the tier +TORRENT_TEST(tracker_tiers_unreachable_tracker) +{ + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_bool(settings_pack::announce_to_all_trackers, false); + test_tracker_tiers(pack, { addr("50.0.0.1") } + , { {"f8e0::1", 0}, {"3.0.0.2", 0}, {"3.0.0.3", 1}, {"3.0.0.4", 1}} + , [](int (&a)[6]) { + TEST_EQUAL(a[0], 0); + TEST_EQUAL(a[1], 2); + TEST_EQUAL(a[2], 0); + TEST_EQUAL(a[3], 0); + TEST_EQUAL(a[4], 0); + TEST_EQUAL(a[5], 0); + }); +} + +// in this test, we have both v6 and v4 connectivity, and we have two trackers +// One is v6 only and one is dual. Since the first tracker was announced to +// using IPv6, the second tracker will *only* be used for IPv4, and not to +// announce IPv6 to again. +TORRENT_TEST(tracker_tiers_v4_and_v6_same_tier) +{ + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_bool(settings_pack::announce_to_all_trackers, false); + test_tracker_tiers(pack, { addr("50.0.0.1"), addr("f8e0::10") } + , { {"ipv6-only-tracker.com", 0}, {"dual-tracker.com", 0}} + , [](int (&a)[6]) { + TEST_EQUAL(a[0], 0); + TEST_EQUAL(a[1], 2); + TEST_EQUAL(a[2], 0); + TEST_EQUAL(a[3], 0); + TEST_EQUAL(a[4], 2); + TEST_EQUAL(a[5], 0); + }); +} + +TORRENT_TEST(tracker_tiers_v4_and_v6_different_tiers) +{ + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_bool(settings_pack::announce_to_all_trackers, false); + test_tracker_tiers(pack, { addr("50.0.0.1"), addr("f8e0::10") } + , { {"ipv6-only-tracker.com", 0}, {"dual-tracker.com", 1}} + , [](int (&a)[6]) { + TEST_EQUAL(a[0], 0); + TEST_EQUAL(a[1], 2); + TEST_EQUAL(a[2], 0); + TEST_EQUAL(a[3], 0); + TEST_EQUAL(a[4], 2); + TEST_EQUAL(a[5], 0); + }); +} + +// in the same scenario as above, if we announce to all trackers, we expect to +// continue to visit all trackers in the tier, and announce to that additional +// IPv6 address as well +TORRENT_TEST(tracker_tiers_v4_and_v6_all_trackers) +{ + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + test_tracker_tiers(pack, { addr("50.0.0.1"), addr("f8e0::10") } + , { {"ipv6-only-tracker.com", 0}, {"dual-tracker.com", 0}} + , [](int (&a)[6]) { + TEST_EQUAL(a[0], 0); + TEST_EQUAL(a[1], 2); + TEST_EQUAL(a[2], 0); + TEST_EQUAL(a[3], 0); + TEST_EQUAL(a[4], 2); + TEST_EQUAL(a[5], 2); + }); +} + +TORRENT_TEST(tracker_tiers_v4_and_v6_different_tiers_all_trackers) +{ + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + test_tracker_tiers(pack, { addr("50.0.0.1"), addr("f8e0::10") } + , { {"ipv6-only-tracker.com", 0}, {"dual-tracker.com", 1}} + , [](int (&a)[6]) { + TEST_EQUAL(a[0], 0); + TEST_EQUAL(a[1], 2); + TEST_EQUAL(a[2], 0); + TEST_EQUAL(a[3], 0); + TEST_EQUAL(a[4], 2); + TEST_EQUAL(a[5], 0); + }); +} + +TORRENT_TEST(tracker_tiers_v4_and_v6_different_tiers_all_tiers) +{ + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_tiers, true); + pack.set_bool(settings_pack::announce_to_all_trackers, false); + test_tracker_tiers(pack, { addr("50.0.0.1"), addr("f8e0::10") } + , { {"ipv6-only-tracker.com", 0}, {"dual-tracker.com", 1}} + , [](int (&a)[6]) { + TEST_EQUAL(a[0], 0); + TEST_EQUAL(a[1], 2); + TEST_EQUAL(a[2], 0); + TEST_EQUAL(a[3], 0); + TEST_EQUAL(a[4], 2); + TEST_EQUAL(a[5], 2); + }); +} + +// TODO: test external IP +// TODO: test with different queuing settings +// TODO: test when a torrent transitions from downloading to finished and +// finished to seeding +// TODO: test that left, downloaded and uploaded are reported correctly + +// TODO: test scrape + diff --git a/simulation/test_transfer.cpp b/simulation/test_transfer.cpp new file mode 100644 index 0000000..ff34ca7 --- /dev/null +++ b/simulation/test_transfer.cpp @@ -0,0 +1,547 @@ +/* + +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 "transfer_sim.hpp" + +using namespace sim; +using namespace lt; + +TORRENT_TEST(socks4_tcp) +{ + run_test( + [](lt::session& ses0, lt::session& ses1) + { + set_proxy(ses0, settings_pack::socks4); + filter_ips(ses1); + }, + [](lt::session&, lt::alert const*) {}, + expect_seed(true) + ); +} + +TORRENT_TEST(socks5_tcp_connect) +{ + run_test( + [](lt::session& ses0, lt::session& ses1) + { + set_proxy(ses0, settings_pack::socks5); + filter_ips(ses1); + }, + [](lt::session&, lt::alert const*) {}, + expect_seed(true) + ); +} + +TORRENT_TEST(encryption_tcp) +{ + run_test( + [](lt::session& ses0, lt::session& ses1) + { enable_enc(ses0); enable_enc(ses1); }, + [](lt::session&, lt::alert const*) {}, + expect_seed(true) + ); +} + +TORRENT_TEST(no_proxy_tcp_ipv6) +{ + run_test( + no_init, + [](lt::session&, lt::alert const*) {}, + expect_seed(true), + tx::ipv6 + ); +} + +TORRENT_TEST(no_proxy_utp_ipv6) +{ + run_test( + [](lt::session& ses0, lt::session& ses1) + { utp_only(ses0); utp_only(ses1); }, + [](lt::session&, lt::alert const*) {}, + expect_seed(true), + tx::ipv6 + ); +} + +// TODO: the socks server does not support IPv6 addresses yet +/* +TORRENT_TEST(socks5_tcp_ipv6) +{ + run_test( + [](lt::session& ses0, lt::session& ses1) + { + set_proxy(ses0, settings_pack::socks5); + filter_ips(ses1); + }, + [](lt::session&, lt::alert const*) {}, + [](std::shared_ptr ses[2]) { + TEST_EQUAL(is_seed(*ses[0]), true); + }, + tx::ipv6 + ); +} +*/ + +TORRENT_TEST(no_proxy_tcp) +{ + run_test( + no_init, + [](lt::session&, lt::alert const*) {}, + expect_seed(true) + ); +} + +TORRENT_TEST(no_proxy_utp) +{ + run_test( + [](lt::session& ses0, lt::session& ses1) + { utp_only(ses0); utp_only(ses1); }, + [](lt::session&, lt::alert const*) {}, + expect_seed(true) + ); +} + +TORRENT_TEST(encryption_utp) +{ + run_test( + [](lt::session& ses0, lt::session& ses1) + { + enable_enc(ses0); + enable_enc(ses1); + utp_only(ses0); + utp_only(ses1); + }, + [](lt::session&, lt::alert const*) {}, + expect_seed(true) + ); +} + +TORRENT_TEST(socks5_utp) +{ + run_test( + [](lt::session& ses0, lt::session& ses1) + { + set_proxy(ses0, settings_pack::socks5); + utp_only(ses0); + filter_ips(ses1); + utp_only(ses1); + }, + [](lt::session&, lt::alert const*) {}, + expect_seed(true) + ); +} + +TORRENT_TEST(socks5_utp_incoming) +{ + run_test( + [](lt::session& ses0, lt::session& ses1) + { + set_proxy(ses1, settings_pack::socks5); + utp_only(ses0); + utp_only(ses1); + filter_ips(ses0); + }, + [](lt::session&, lt::alert const*) {}, + expect_seed(true), + tx::connect_proxy + ); +} + +TORRENT_TEST(socks5_utp_circumvent_proxy_reject) +{ + run_test( + [](lt::session& ses0, lt::session& ses1) + { + set_proxy(ses1, settings_pack::socks5); + utp_only(ses0); + utp_only(ses1); + }, + [](lt::session&, lt::alert const*) {}, + expect_seed(false) + ); +} + +// if we're not proxying peer connections, it's OK to accept incoming +// connections +TORRENT_TEST(socks5_utp_circumvent_proxy_ok) +{ + run_test( + [](lt::session& ses0, lt::session& ses1) + { + set_proxy(ses1, settings_pack::socks5, {}, false); + utp_only(ses0); + utp_only(ses1); + }, + [](lt::session&, lt::alert const*) {}, + + // the UDP socket socks5 proxy support doesn't allow accepting direct + // connections, circumventing the proxy, so this transfer will fail, + // even though it would be reasonable for it to pass as well + expect_seed(false) + ); +} + +TORRENT_TEST(http_tcp_circumvent_proxy_reject) +{ + run_test( + [](lt::session& ses0, lt::session& ses1) + { + set_proxy(ses1, settings_pack::http); + }, + [](lt::session&, lt::alert const*) {}, + expect_seed(false) + ); +} + +// if we're not proxying peer connections, it's OK to accept incoming +// connections +TORRENT_TEST(http_tcp_circumvent_proxy_ok) +{ + run_test( + [](lt::session& ses0, lt::session& ses1) + { + set_proxy(ses1, settings_pack::http, {}, false); + }, + [](lt::session&, lt::alert const*) {}, + expect_seed(true) + ); +} + +// the purpose of these tests is to make sure that the sessions can't actually +// talk directly to each other. i.e. they are negative tests. If they can talk +// directly to each other, all other tests in here may be broken. +TORRENT_TEST(no_proxy_tcp_banned) +{ + run_test( + [](lt::session&, lt::session& ses1) { filter_ips(ses1); }, + [](lt::session&, lt::alert const*) {}, + expect_seed(false) + ); +} + +TORRENT_TEST(no_proxy_utp_banned) +{ + run_test( + [](lt::session& ses0, lt::session& ses1) + { utp_only(ses0); utp_only(ses1); filter_ips(ses1); }, + [](lt::session&, lt::alert const*) {}, + expect_seed(false) + ); +} + +TORRENT_TEST(piece_extent_affinity) +{ + run_test( + [](lt::session& ses0, lt::session& ses1) + { + settings_pack p; + p.set_bool(settings_pack::piece_extent_affinity, true); + ses0.apply_settings(p); + ses1.apply_settings(p); + }, + [](lt::session&, lt::alert const*) {}, + expect_seed(true) + ); +} + +TORRENT_TEST(is_finished) +{ + run_test(no_init + , [](lt::session& ses, lt::alert const* a) { + if (alert_cast(a)) + { + TEST_EQUAL(is_finished(ses), false); + std::vector prio(4, dont_download); + ses.get_torrents()[0].prioritize_files(prio); + // applying the priorities is asynchronous. the torrent may not + // finish immediately + } + }, + [](std::shared_ptr ses[2]) { + TEST_EQUAL(is_finished(*ses[0]), true); + TEST_EQUAL(is_finished(*ses[1]), true); + } + ); +} + +TORRENT_TEST(v1_only_magnet) +{ + std::set passed; + run_test(no_init + , record_finished_pieces(passed) + , expect_seed(true) + , tx::v1_only | tx::magnet_download + ); + TEST_EQUAL(passed.size(), 11); +} + +TORRENT_TEST(disk_full) +{ + run_test(no_init + , [](lt::session&, lt::alert const*) {} + // the disk filled up, we failed to complete the download + , expect_seed(false) + , {} + , test_disk().set_space_left(5 * lt::default_block_size) + ); +} + +TORRENT_TEST(disk_full_recover) +{ + run_test( + [](lt::session& ses0, lt::session&) + { + settings_pack p; + p.set_int(settings_pack::optimistic_disk_retry, 30); + ses0.apply_settings(p); + }, + [](lt::session&, lt::alert const* a) { + if (auto ta = alert_cast(a)) + { + // the torrent has to be auto-managed in order to automatically + // leave upload mode after it hits disk-full + ta->handle.set_flags(torrent_flags::auto_managed); + } + } + // the disk filled up, we failed to complete the download, but then the + // disk recovered and we completed it + , expect_seed(true) + , {} + , test_disk().set_space_left(10 * lt::default_block_size).set_recover_full_disk() + , test_disk() + , lt::seconds(65) + ); +} + +TORRENT_TEST(disk_full_recover_large_pieces) +{ + run_test( + [](lt::session& ses0, lt::session&) + { + settings_pack p; + p.set_int(settings_pack::optimistic_disk_retry, 30); + ses0.apply_settings(p); + }, + [](lt::session&, lt::alert const* a) { + if (auto ta = alert_cast(a)) + { + // the torrent has to be auto-managed in order to automatically + // leave upload mode after it hits disk-full + ta->handle.set_flags(torrent_flags::auto_managed); + } + } + // the disk filled up, we failed to complete the download, but then the + // disk recovered and we completed it + , expect_seed(true) + , tx::large_pieces + , test_disk().set_space_left(10 * lt::default_block_size).set_recover_full_disk() + , test_disk() + , lt::seconds(70) + ); +} + +// Below is a series of tests to transfer torrents with varying pad-file related +// traits +void run_torrent_test(std::shared_ptr ti) +{ + using asio::ip::address; + address peer0 = addr("50.0.0.1"); + address peer1 = addr("50.0.0.2"); + + // setup the simulation + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + sim::asio::io_context ios0 { sim, peer0 }; + sim::asio::io_context ios1 { sim, peer1 }; + + lt::session_proxy zombie[2]; + + lt::session_params params; + // setup settings pack to use for the session (customization point) + lt::settings_pack& pack = params.settings; + pack = settings(); + pack.set_bool(settings_pack::disable_hash_checks, false); + + // disable utp by default + pack.set_bool(settings_pack::enable_outgoing_utp, false); + pack.set_bool(settings_pack::enable_incoming_utp, false); + + // disable encryption by default + 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_plaintext); + + pack.set_str(settings_pack::listen_interfaces, "50.0.0.1:6881"); + + // create session + std::shared_ptr ses[2]; + + // session 0 is a downloader, session 1 is a seed + + params.disk_io_constructor = test_disk(); + ses[0] = std::make_shared(params, ios0); + + pack.set_str(settings_pack::listen_interfaces, "50.0.0.2:6881"); + + params.disk_io_constructor = test_disk().set_files(existing_files_mode::full_valid); + ses[1] = std::make_shared(params, ios1); + + // only monitor alerts for session 0 (the downloader) + print_alerts(*ses[0], [=](lt::session& ses, lt::alert const* a) { + if (auto ta = alert_cast(a)) + { + ta->handle.connect_peer(lt::tcp::endpoint(peer1, 6881)); + } + }, 0); + + print_alerts(*ses[1], [](lt::session&, lt::alert const*){}, 1); + + lt::add_torrent_params atp; + atp.ti = ti; + atp.save_path = "."; + + atp.flags &= ~lt::torrent_flags::auto_managed; + atp.flags &= ~lt::torrent_flags::paused; + + ses[1]->async_add_torrent(atp); + auto torrent = atp.ti; + + ses[0]->async_add_torrent(atp); + + sim::timer t(sim, lt::seconds(10), [&](boost::system::error_code const&) + { + auto h = ses[0]->get_torrents(); + auto ti = h[0].torrent_file_with_hashes(); + + if (ti->v2()) + TEST_EQUAL(ti->v2_piece_hashes_verified(), true); + + auto downloaded = serialize(*ti); + auto added = serialize(*torrent); + TEST_CHECK(downloaded == added); + + TEST_CHECK(is_seed(*ses[0])); + TEST_CHECK(is_seed(*ses[1])); + + h[0].force_recheck(); + }); + + sim::timer t2(sim, lt::minutes(1), [&](boost::system::error_code const&) + { + // shut down + int idx = 0; + for (auto& s : ses) + { + zombie[idx++] = s->abort(); + s.reset(); + } + }); + + sim.run(); +} + +namespace { + +std::shared_ptr test_torrent(lt::file_storage fs, lt::create_flags_t const flags) +{ + lt::create_torrent ct(fs, fs.piece_length(), flags); + lt::settings_pack pack; + lt::error_code ec; + lt::set_piece_hashes(ct, "", pack, test_disk().set_files(existing_files_mode::full_valid) + , [](lt::piece_index_t p) { std::cout << "."; std::cout.flush();}, ec); + + auto e = ct.generate(); + return std::make_shared(e); +} + +} + +TORRENT_TEST(simple_torrent) +{ + run_torrent_test(test_torrent(make_files( + {{0x3ff0, false}, {0x10, true}}, 0x4000), {})); +} + +TORRENT_TEST(odd_last_pad_file) +{ + run_torrent_test(test_torrent(make_files( + {{0x4100, false}, {0x10, true}}, 0x4000), {})); +} + +TORRENT_TEST(small_piece_size) +{ + run_torrent_test(test_torrent(make_files( + {{0x3ff0, false}, {0x10, true}}, 0x2000), {})); +} + +TORRENT_TEST(odd_piece_size) +{ + run_torrent_test(test_torrent(make_files( + {{0x1ffe, false}, {0x1, true}}, 0x1fff), {})); +} + +TORRENT_TEST(large_pad_file) +{ + run_torrent_test(test_torrent(make_files( + {{0x5000, false}, {0x100000000 - 0x5000, true}}, 0x100000), {})); +} + +TORRENT_TEST(unaligned_pad_file) +{ + run_torrent_test(test_torrent(make_files( + {{0x3fff, false}, {0x10, true}}, 0x4000), {})); +} + +TORRENT_TEST(piece_size_pad_file) +{ + run_torrent_test(test_torrent(make_files( + {{0x8000, false}, {0x8000, true}}, 0x8000), {})); +} + +TORRENT_TEST(block_size_pad_file) +{ + run_torrent_test(test_torrent(make_files( + {{0x4000, false}, {0x4000, true}}, 0x4000), {})); +} + +TORRENT_TEST(back_to_back_pad_file) +{ + run_torrent_test(test_torrent(make_files( + {{0x3000, false}, {0x800, true}, {0x800, true}}, 0x4000), {})); +} + +TORRENT_TEST(small_file_large_piece) +{ + run_torrent_test(test_torrent(make_files( + {{0x833ed, false}, {0x7cc13, true}, {0x3d, false}, {0x7ffc3, true}, {0x14000, false}}, 0x80000), {})); +} diff --git a/simulation/test_transfer_matrix.cpp b/simulation/test_transfer_matrix.cpp new file mode 100644 index 0000000..8f393a3 --- /dev/null +++ b/simulation/test_transfer_matrix.cpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "transfer_sim.hpp" + +void run_matrix_test(test_transfer_flags_t const flags, existing_files_mode const files, bool const corruption) +{ + std::cout << "\n\nTEST CASE: " + << ((flags & tx::small_pieces) ? "small-pieces" : (flags & tx::large_pieces) ? "large-pieces" : "normal-pieces") + << "-" << (corruption ? "corruption" : "valid") + << "-" << ((flags & tx::v2_only) ? "v2_only" : (flags & tx::v1_only) ? "v1_only" : "hybrid") + << "-" << ((flags & tx::magnet_download) ? "magnet" : "torrent") + << "-" << ((flags & tx::multiple_files) ? "multi_file" : "single_file") + << "-" << files + << "\n\n"; + + auto downloader_disk = test_disk().set_files(files); + auto seeder_disk = test_disk(); + if (corruption) seeder_disk = seeder_disk.send_corrupt_data(num_pieces(flags) / 4 * blocks_per_piece(flags)); + std::set passed; + run_test(no_init + , record_finished_pieces(passed) + , expect_seed(!corruption) + , flags + , downloader_disk + , seeder_disk + ); + + int const expected_pieces = num_pieces(flags); + + // we we send some corrupt pieces, it's not straight-forward to predict + // exactly how many will pass the hash check, since a failure will cause + // a re-request and also a request of the block hashes (for v2 torrents) + if (corruption) + { + TEST_CHECK(int(passed.size()) < expected_pieces); + } + else + { + TEST_EQUAL(int(passed.size()), expected_pieces); + } +} + +TORRENT_TEST(transfer_matrix) +{ + using fm = existing_files_mode; + + for (test_transfer_flags_t piece_size : {test_transfer_flags_t{}, tx::small_pieces, tx::large_pieces}) + for (bool corruption : {false, true}) + for (test_transfer_flags_t bt_version : {test_transfer_flags_t{}, tx::v2_only, tx::v1_only}) + for (test_transfer_flags_t magnet : {test_transfer_flags_t{}, tx::magnet_download}) + for (test_transfer_flags_t multi_file : {test_transfer_flags_t{}, tx::multiple_files}) + for (fm files : {fm::no_files, fm::full_invalid, fm::partial_valid}) + { + // this will clear the history of all output we've printed so far. + // if we encounter an error from now on, we'll only print the relevant + // iteration + ::unit_test::reset_output(); + + // re-seed the random engine each iteration, to make the runs + // deterministic + lt::aux::random_engine().seed(0x23563a7f); + + run_matrix_test(piece_size | bt_version | magnet | multi_file, files, corruption); + if (::unit_test::g_test_failures > 0) return; + } +} + diff --git a/simulation/test_utp.cpp b/simulation/test_utp.cpp new file mode 100644 index 0000000..33fedec --- /dev/null +++ b/simulation/test_utp.cpp @@ -0,0 +1,232 @@ +/* + +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/settings_pack.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/time.hpp" // for clock_type +#include "libtorrent/aux_/utp_stream.hpp" +#include "libtorrent/session_stats.hpp" + +#include "test.hpp" +#include "utils.hpp" +#include "setup_swarm.hpp" +#include "settings.hpp" +#include +#include +#include +#include + +#include "simulator/packet.hpp" + +using namespace lt; + +namespace { + +struct pppoe_config final : sim::default_config +{ + int path_mtu(address, address) override + { + // this is the size left after IP and UDP headers are deducted + return 1464; + } +}; + +std::int64_t metric(std::vector const& counters, char const* key) +{ + auto const idx = lt::find_metric_idx(key); + return (idx < 0) ? -1 : counters[idx]; +} + +std::vector utp_test(sim::configuration& cfg) +{ + sim::simulation sim{cfg}; + + std::vector cnt; + + setup_swarm(2, swarm_test::upload | swarm_test::large_torrent | swarm_test::no_auto_stop, sim + // add session + , [](lt::settings_pack& pack) { + // force uTP connection + utp_only(pack); + } + // add torrent + , [](lt::add_torrent_params& params) { + params.flags |= torrent_flags::seed_mode; + } + // on alert + , [&](lt::alert const* a, lt::session& ses) { + if (auto ss = alert_cast(a)) + cnt.assign(ss->counters().begin(), ss->counters().end()); + } + // terminate + , [&](int const ticks, lt::session& s) -> bool + { + if (ticks == 100) + s.post_session_stats(); + + if (ticks > 100) + { + if (is_seed(s)) return true; + + TEST_ERROR("timeout"); + return true; + } + return false; + }); + return cnt; +} +} + +// TODO: 3 simulate non-congestive packet loss +// TODO: 3 simulate unpredictible latencies +// TODO: 3 simulate proper (taildrop) queues (perhaps even RED and BLUE) + +// The counters checked by these tests are proxies for the expected behavior. If +// they change, ensure the utp log and graph plot by parse_utp_log.py look good +// still! + +TORRENT_TEST(utp_pmtud) +{ +#if TORRENT_UTP_LOG + lt::aux::set_utp_stream_logging(true); +#endif + + pppoe_config cfg; + + std::vector cnt = utp_test(cfg); + + // This is the one MTU probe that's lost. Note that fast-retransmit packets + // (nor MTU-probes) are treated as congestion. Only packets treated as + // congestion count as utp_packet_loss. + TEST_EQUAL(metric(cnt, "utp.utp_fast_retransmit"), 2); + TEST_EQUAL(metric(cnt, "utp.utp_packet_resend"), 2); + + TEST_EQUAL(metric(cnt, "utp.utp_packet_loss"), 0); + + // TODO: 3 This timeout happens at shutdown. It's not very clean + TEST_EQUAL(metric(cnt, "utp.utp_timeout"), 1); + + TEST_EQUAL(metric(cnt, "utp.utp_packets_in"), 611); + TEST_EQUAL(metric(cnt, "utp.utp_payload_pkts_in"), 23); + + TEST_EQUAL(metric(cnt, "utp.utp_packets_out"), 612); + + // we don't expect any invalid packets, since we're talking to ourself + TEST_EQUAL(metric(cnt, "utp.utp_invalid_pkts_in"), 0); + TEST_EQUAL(metric(cnt, "utp.utp_redundant_pkts_in"), 0); +} + +TORRENT_TEST(utp_plain) +{ +#if TORRENT_UTP_LOG + lt::aux::set_utp_stream_logging(true); +#endif + + // the available bandwidth is so high the test never bumps up against it + sim::default_config cfg; + + std::vector cnt = utp_test(cfg); + + TEST_EQUAL(metric(cnt, "utp.utp_packet_loss"), 0); + TEST_EQUAL(metric(cnt, "utp.utp_timeout"), 1); + TEST_EQUAL(metric(cnt, "utp.utp_fast_retransmit"), 0); + TEST_EQUAL(metric(cnt, "utp.utp_packet_resend"), 0); + + TEST_EQUAL(metric(cnt, "utp.utp_packets_in"), 609); + TEST_EQUAL(metric(cnt, "utp.utp_payload_pkts_in"), 23); + + TEST_EQUAL(metric(cnt, "utp.utp_packets_out"), 608); + + // we don't expect any invalid packets, since we're talking to ourself + TEST_EQUAL(metric(cnt, "utp.utp_invalid_pkts_in"), 0); + TEST_EQUAL(metric(cnt, "utp.utp_redundant_pkts_in"), 0); +} + +TORRENT_TEST(utp_buffer_bloat) +{ +#if TORRENT_UTP_LOG + lt::aux::set_utp_stream_logging(true); +#endif + + // 50 kB/s, 500 kB send buffer size. That's 10 seconds + dsl_config cfg(50, 500000); + + std::vector cnt = utp_test(cfg); + + TEST_EQUAL(metric(cnt, "utp.utp_packet_loss"), 0); + TEST_EQUAL(metric(cnt, "utp.utp_timeout"), 1); + TEST_EQUAL(metric(cnt, "utp.utp_fast_retransmit"), 0); + TEST_EQUAL(metric(cnt, "utp.utp_packet_resend"), 0); + + TEST_EQUAL(metric(cnt, "utp.utp_samples_above_target"), 425); + TEST_EQUAL(metric(cnt, "utp.utp_samples_below_target"), 156); + + TEST_EQUAL(metric(cnt, "utp.utp_packets_in"), 646); + TEST_EQUAL(metric(cnt, "utp.utp_payload_pkts_in"), 62); + + TEST_EQUAL(metric(cnt, "utp.utp_packets_out"), 645); + + // we don't expect any invalid packets, since we're talking to ourself + TEST_EQUAL(metric(cnt, "utp.utp_invalid_pkts_in"), 0); + TEST_EQUAL(metric(cnt, "utp.utp_redundant_pkts_in"), 0); +} + +// low bandwidth limit, but virtually no buffer +TORRENT_TEST(utp_straw) +{ +#if TORRENT_UTP_LOG + lt::aux::set_utp_stream_logging(true); +#endif + + // 50 kB/s, 500 kB send buffer size. That's 10 seconds + dsl_config cfg(50, 1500); + + std::vector cnt = utp_test(cfg); + + TEST_EQUAL(metric(cnt, "utp.utp_packet_loss"), 69); + TEST_EQUAL(metric(cnt, "utp.utp_timeout"), 29); + TEST_EQUAL(metric(cnt, "utp.utp_fast_retransmit"), 72); + TEST_EQUAL(metric(cnt, "utp.utp_packet_resend"), 133); + + TEST_EQUAL(metric(cnt, "utp.utp_samples_above_target"), 0); + TEST_EQUAL(metric(cnt, "utp.utp_samples_below_target"), 277); + + TEST_EQUAL(metric(cnt, "utp.utp_packets_in"), 429); + TEST_EQUAL(metric(cnt, "utp.utp_payload_pkts_in"), 55); + + TEST_EQUAL(metric(cnt, "utp.utp_packets_out"), 563); + + // we don't expect any invalid packets, since we're talking to ourself + TEST_EQUAL(metric(cnt, "utp.utp_invalid_pkts_in"), 0); + TEST_EQUAL(metric(cnt, "utp.utp_redundant_pkts_in"), 0); +} diff --git a/simulation/test_web_seed.cpp b/simulation/test_web_seed.cpp new file mode 100644 index 0000000..d38d2a2 --- /dev/null +++ b/simulation/test_web_seed.cpp @@ -0,0 +1,869 @@ +/* + +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/session.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/aux_/path.hpp" +#include "simulator/http_server.hpp" +#include "simulator/http_proxy.hpp" +#include "settings.hpp" +#include "libtorrent/create_torrent.hpp" +#include "simulator/simulator.hpp" +#include "setup_swarm.hpp" +#include "utils.hpp" +#include "make_proxy_settings.hpp" +#include "simulator/utils.hpp" +#include +#include + +using namespace sim; +using namespace lt; + + +int const piece_size = 0x4000; + +add_torrent_params create_torrent(file_storage& fs, bool const v1_only = false) +{ + lt::create_torrent t(fs, piece_size + , v1_only ? create_torrent::v1_only : create_flags_t{}); + + std::vector piece; + piece.reserve(fs.piece_length()); + piece_index_t const num = fs.end_piece(); + for (piece_index_t i(0); i < num; ++i) + { + int k = 0; + std::vector files = fs.map_block(i, 0, fs.piece_size(i)); + for (auto& f : files) + { + if (fs.pad_file_at(f.file_index)) + { + for (int j = 0; j < f.size; ++j, ++k) + piece.push_back('\0'); + } + else + { + for (int j = 0; j < f.size; ++j, ++k) + piece.push_back((k % 26) + 'A'); + } + } + + t.set_hash(i, hasher(piece).final()); + if (!v1_only) + { + piece_index_t const file_first_piece(int(fs.file_offset(files[0].file_index) / fs.piece_length())); + t.set_hash2(files[0].file_index, i - file_first_piece + , hasher256(span(piece).first(files[0].size)).final()); + } + piece.clear(); + } + + std::vector tmp; + std::back_insert_iterator> out(tmp); + + entry tor = t.generate(); + + bencode(out, tor); + add_torrent_params ret; + ret.ti = std::make_shared(tmp, from_span); + ret.flags &= ~lt::torrent_flags::auto_managed; + ret.flags &= ~lt::torrent_flags::paused; + ret.save_path = "."; + return ret; +} + +struct sim_config : sim::default_config +{ + explicit sim_config() {} + + chrono::high_resolution_clock::duration hostname_lookup( + asio::ip::address const& requestor + , std::string hostname + , std::vector& result + , boost::system::error_code& ec) override + { + auto const ret = duration_cast(chrono::milliseconds(100)); + if (hostname == "2.server.com") + { + result.push_back(make_address_v4("2.2.2.2")); + return ret; + } + if (hostname == "2.xn--server-.com") + { + result.push_back(make_address_v4("2.2.2.2")); + return ret; + } + if (hostname == "3.server.com") + { + result.push_back(make_address_v4("3.3.3.3")); + return ret; + } + if (hostname == "3.xn--server-.com") + { + result.push_back(make_address_v4("3.3.3.3")); + return ret; + } + if (hostname == "local-network.com") + { + result.push_back(make_address_v4("192.168.1.13")); + return ret; + } + + return default_config::hostname_lookup(requestor, hostname, result, ec); + } +}; + +// this is the general template for these tests. create the session with custom +// settings (Settings), set up the test, by adding torrents with certain +// arguments (Setup), run the test and verify the end state (Test) +template +void run_test(Setup const& setup + , HandleAlerts const& on_alert + , Test const& test + , lt::seconds const timeout = lt::seconds{100}) +{ + // setup the simulation + sim_config network_cfg; + sim::simulation sim{network_cfg}; + std::unique_ptr ios = make_io_context(sim, 0); + lt::session_proxy zombie; + + lt::settings_pack pack = settings(); + // create session + std::shared_ptr ses = std::make_shared(pack, *ios); + + // set up test, like adding torrents (customization point) + setup(*ses); + + // only monitor alerts for session 0 (the downloader) + print_alerts(*ses, [=](lt::session& ses, lt::alert const* a) { + on_alert(ses, a); + }); + + // set up a timer to fire later, to verify everything we expected to happen + // happened + sim::timer t(sim, timeout, [&](boost::system::error_code const&) + { + std::printf("shutting down\n"); + // shut down + zombie = ses->abort(); + ses.reset(); + }); + + test(sim, *ses); +} + +TORRENT_TEST(single_file) +{ + using namespace lt; + + file_storage fs; + fs.add_file("abc'abc", 0x8000); // this filename will have to be escaped + lt::add_torrent_params params = ::create_torrent(fs); + params.url_seeds.push_back("http://2.2.2.2:8080/"); + + bool expected = false; + run_test( + [¶ms](lt::session& ses) + { + ses.async_add_torrent(params); + }, + [](lt::session&, lt::alert const*) {}, + [&expected](sim::simulation& sim, lt::session&) + { + sim::asio::io_context web_server(sim, make_address_v4("2.2.2.2")); + // listen on port 8080 + sim::http_server http(web_server, 8080); + + // make sure the requested file is correctly escaped + http.register_handler("/abc%27abc" + , [&expected](std::string method, std::string req + , std::map&) + { + expected = true; + return sim::send_response(404, "Not Found", 0); + }); + + sim.run(); + } + ); + + TEST_CHECK(expected); +} + +TORRENT_TEST(multi_file) +{ + using namespace lt; + file_storage fs; + fs.add_file(combine_path("foo", "abc'abc"), 0x8000); // this filename will have to be escaped + fs.add_file(combine_path("foo", "bar"), 0x3000); + lt::add_torrent_params params = ::create_torrent(fs); + params.url_seeds.push_back("http://2.2.2.2:8080/"); + + std::array expected{{ false, false }}; + run_test( + [¶ms](lt::session& ses) + { + ses.async_add_torrent(params); + }, + [](lt::session&, lt::alert const*) {}, + [&expected](sim::simulation& sim, lt::session&) + { + sim::asio::io_context web_server(sim, make_address_v4("2.2.2.2")); + // listen on port 8080 + sim::http_server http(web_server, 8080); + + // make sure the requested file is correctly escaped + http.register_handler("/foo/abc%27abc" + , [&expected](std::string, std::string, std::map&) + { + expected[0] = true; + return sim::send_response(404, "not found", 0); + }); + http.register_handler("/foo/bar" + , [&expected](std::string, std::string, std::map&) + { + expected[1] = true; + return sim::send_response(404, "not found", 0); + }); + + sim.run(); + } + ); + + TEST_CHECK(expected[0]); + TEST_CHECK(expected[1]); +} + +std::string generate_content(lt::file_storage const& fs, file_index_t file + , std::int64_t offset, std::int64_t len) +{ + std::string ret; + ret.reserve(lt::aux::numeric_cast(len)); + std::int64_t const file_offset = fs.file_offset(file); + int const piece_sz = fs.piece_length(); + for (std::int64_t i = offset + file_offset; i < offset + file_offset + len; ++i) + ret.push_back(((i % piece_sz) % 26) + 'A'); + return ret; +} + +void serve_content_for(sim::http_server& http, std::string const& path + , lt::file_storage const& fs, file_index_t const file) +{ + http.register_content(path, fs.file_size(file_index_t(file)) + , [&fs,file](std::int64_t offset, std::int64_t len) + { return generate_content(fs, file, offset, len); }); +} + +// test redirecting *unaligned* files to the same server still working. i.e. the +// second redirect is added to the same web-seed entry as the first one +TORRENT_TEST(unaligned_file_redirect) +{ + using namespace lt; + file_storage fs; + fs.add_file(combine_path("foo", "1"), 0xc030); + fs.add_file(combine_path("foo", "2"), 0xc030); + lt::add_torrent_params params = ::create_torrent(fs, true); + params.url_seeds.push_back("http://2.2.2.2:8080/"); + + bool seeding = false; + + run_test( + [¶ms](lt::session& ses) + { + ses.async_add_torrent(params); + }, + [&](lt::session&, lt::alert const* alert) { + if (lt::alert_cast(alert)) + seeding = true; + }, + [&fs](sim::simulation& sim, lt::session&) + { + // http1 is the root web server that will just redirect requests to + // other servers + sim::asio::io_context web_server1(sim, make_address_v4("2.2.2.2")); + sim::http_server http1(web_server1, 8080); + // redirect file 1 and file 2 to the same servers + http1.register_redirect("/foo/1", "http://3.3.3.3:4444/bla/file1"); + http1.register_redirect("/foo/2", "http://3.3.3.3:4444/bar/file2"); + + // server for serving the content + sim::asio::io_context web_server2(sim, make_address_v4("3.3.3.3")); + sim::http_server http2(web_server2, 4444); + serve_content_for(http2, "/bla/file1", fs, file_index_t(0)); + serve_content_for(http2, "/bar/file2", fs, file_index_t(1)); + + sim.run(); + } + ); + + TEST_EQUAL(seeding, true); +} + +// test redirecting *unaligned* but padded files to separate servers +TORRENT_TEST(multi_file_redirect_pad_files) +{ + using namespace lt; + file_storage fs_; + fs_.add_file(combine_path("foo", "1"), 0xc030); + fs_.add_file(combine_path("foo", "2"), 0xc030); + // true means use padfiles + lt::add_torrent_params params = ::create_torrent(fs_); + params.url_seeds.push_back("http://2.2.2.2:8080/"); + + // since the final torrent is different than what we built (because of pad + // files), ask about it. + file_storage const& fs = params.ti->files(); + + bool seeding = false; + + run_test( + [¶ms](lt::session& ses) + { + ses.async_add_torrent(params); + }, + [&](lt::session&, lt::alert const* alert) { + if (lt::alert_cast(alert)) + seeding = true; + }, + [&fs](sim::simulation& sim, lt::session&) + { + // http1 is the root web server that will just redirect requests to + // other servers + sim::asio::io_context web_server1(sim, make_address_v4("2.2.2.2")); + sim::http_server http1(web_server1, 8080); + // redirect file 1 and file 2 to different servers + http1.register_redirect("/foo/1", "http://3.3.3.3:4444/bla/file1"); + http1.register_redirect("/foo/2", "http://4.4.4.4:9999/bar/file2"); + + // server for file 1 + sim::asio::io_context web_server2(sim, make_address_v4("3.3.3.3")); + sim::http_server http2(web_server2, 4444); + serve_content_for(http2, "/bla/file1", fs, file_index_t(0)); + + // server for file 2 + sim::asio::io_context web_server3(sim, make_address_v4("4.4.4.4")); + sim::http_server http3(web_server3, 9999); + serve_content_for(http3, "/bar/file2", fs, file_index_t(2)); + + sim.run(); + } + ); + + TEST_EQUAL(seeding, true); +} +// test that a web seed can redirect files to separate web servers (as long as +// they are piece aligned) +TORRENT_TEST(multi_file_redirect) +{ + using namespace lt; + file_storage fs; + fs.add_file(combine_path("foo", "1"), 0xc000); + fs.add_file(combine_path("foo", "2"), 0xc030); + lt::add_torrent_params params = ::create_torrent(fs); + params.url_seeds.push_back("http://2.2.2.2:8080/"); + + bool seeding = false; + + run_test( + [¶ms](lt::session& ses) + { + ses.async_add_torrent(params); + }, + [&](lt::session&, lt::alert const* alert) { + if (lt::alert_cast(alert)) + seeding = true; + }, + [&fs](sim::simulation& sim, lt::session&) + { + // http1 is the root web server that will just redirect requests to + // other servers + sim::asio::io_context web_server1(sim, make_address_v4("2.2.2.2")); + sim::http_server http1(web_server1, 8080); + // redirect file 1 and file 2 to different servers + http1.register_redirect("/foo/1", "http://3.3.3.3:4444/bla/file1"); + http1.register_redirect("/foo/2", "http://4.4.4.4:9999/bar/file2"); + + // server for file 1 + sim::asio::io_context web_server2(sim, make_address_v4("3.3.3.3")); + sim::http_server http2(web_server2, 4444); + serve_content_for(http2, "/bla/file1", fs, file_index_t(0)); + + // server for file 2 + sim::asio::io_context web_server3(sim, make_address_v4("4.4.4.4")); + sim::http_server http3(web_server3, 9999); + serve_content_for(http3, "/bar/file2", fs, file_index_t(1)); + + sim.run(); + } + ); + + TEST_EQUAL(seeding, true); +} + +// test web_seed redirect through proxy +TORRENT_TEST(multi_file_redirect_through_proxy) +{ + using namespace lt; + file_storage fs; + fs.add_file(combine_path("foo", "1"), 0xc000); + fs.add_file(combine_path("foo", "2"), 0xc030); + lt::add_torrent_params params = ::create_torrent(fs); + params.url_seeds.push_back("http://2.2.2.2:8080/"); + + bool seeding = false; + + run_test( + [¶ms](lt::session& ses) + { + settings_pack pack; + + pack.set_int(settings_pack::proxy_type, settings_pack::http); + pack.set_str(settings_pack::proxy_hostname, "50.50.50.50"); + pack.set_str(settings_pack::proxy_username, "testuser"); + pack.set_str(settings_pack::proxy_password, "testpass"); + pack.set_int(settings_pack::proxy_port, 4445); + pack.set_bool(settings_pack::proxy_hostnames, true); + ses.apply_settings(pack); + + ses.async_add_torrent(params); + }, + [&](lt::session&, lt::alert const* alert) { + if (lt::alert_cast(alert)) { + seeding = true; + } + }, + [&fs](sim::simulation& sim, lt::session&) + { + sim::asio::io_context proxy_ios(sim, make_address_v4("50.50.50.50")); + sim::http_proxy http_p(proxy_ios, 4445); + + // http1 is the root web server that will just redirect requests to + // other servers + sim::asio::io_context web_server1(sim, make_address_v4("2.2.2.2")); + sim::http_server http1(web_server1, 8080); + // redirect file 1 and file 2 to different servers + http1.register_redirect("/foo/1", "http://3.3.3.3:4444/bla/file1"); + http1.register_redirect("/foo/2", "http://4.4.4.4:9999/bar/file2"); + + // server for file 1 + sim::asio::io_context web_server2(sim, make_address_v4("3.3.3.3")); + sim::http_server http2(web_server2, 4444); + serve_content_for(http2, "/bla/file1", fs, file_index_t(0)); + + // server for file 2 + sim::asio::io_context web_server3(sim, make_address_v4("4.4.4.4")); + sim::http_server http3(web_server3, 9999); + serve_content_for(http3, "/bar/file2", fs, file_index_t(1)); + + sim.run(); + } + ); + + TEST_EQUAL(seeding, true); +} + +// this is expected to fail, since the files are not aligned and redirected to +// separate servers, without pad files +TORRENT_TEST(multi_file_unaligned_redirect) +{ + using namespace lt; + file_storage fs; + fs.add_file(combine_path("foo", "1"), 0xc030); + fs.add_file(combine_path("foo", "2"), 0xc030); + lt::add_torrent_params params = ::create_torrent(fs, true); + params.url_seeds.push_back("http://2.2.2.2:8080/"); + + run_test( + [¶ms](lt::session& ses) + { + ses.async_add_torrent(params); + }, + [&](lt::session&, lt::alert const* alert) { + // We don't expect to get this aslert + TEST_CHECK(lt::alert_cast(alert) == nullptr); + }, + [&fs](sim::simulation& sim, lt::session&) + { + // http1 is the root web server that will just redirect requests to + // other servers + sim::asio::io_context web_server1(sim, make_address_v4("2.2.2.2")); + sim::http_server http1(web_server1, 8080); + // redirect file 1 and file 2 to different servers + http1.register_redirect("/foo/1", "http://3.3.3.3:4444/bla/file1"); + http1.register_redirect("/foo/2", "http://4.4.4.4:9999/bar/file2"); + + // server for file 1 + sim::asio::io_context web_server2(sim, make_address_v4("3.3.3.3")); + sim::http_server http2(web_server2, 4444); + serve_content_for(http2, "/bla/file1", fs, file_index_t(0)); + + // server for file 2 + sim::asio::io_context web_server3(sim, make_address_v4("4.4.4.4")); + sim::http_server http3(web_server3, 9999); + serve_content_for(http3, "/bar/file2", fs, file_index_t(1)); + + sim.run(); + } + ); +} +TORRENT_TEST(urlseed_timeout) +{ + bool timeout = false; + run_test( + [](lt::session& ses) + { + file_storage fs; + fs.add_file("timeout_test", 0x8000); + lt::add_torrent_params params = ::create_torrent(fs); + params.url_seeds.push_back("http://2.2.2.2:8080/"); + params.flags &= ~lt::torrent_flags::auto_managed; + params.flags &= ~lt::torrent_flags::paused; + params.save_path = "."; + ses.async_add_torrent(params); + }, + [&timeout](lt::session&, lt::alert const* alert) { + const lt::peer_disconnected_alert *pda = lt::alert_cast(alert); + if (pda && pda->error == errors::timed_out_inactivity){ + timeout = true; + } + }, + [](sim::simulation& sim, lt::session&) + { + sim::asio::io_context web_server(sim, make_address_v4("2.2.2.2")); + + // listen on port 8080 + sim::http_server http(web_server, 8080); + http.register_stall_handler("/timeout_test"); + sim.run(); + } + ); + TEST_EQUAL(timeout, true); +} + +// check for correct handle of unexpected http status response. +// with disabled "close_redundant_connections" alive web server connection +// may be closed in such manner. +TORRENT_TEST(no_close_redudant_webseed) +{ + using namespace lt; + + file_storage fs; + fs.add_file("file1", 1); + lt::add_torrent_params params = ::create_torrent(fs); + params.url_seeds.push_back("http://2.2.2.2:8080/"); + + bool expected = false; + run_test( + [¶ms](lt::session& ses) + { + lt::settings_pack pack; + pack.set_bool(settings_pack::close_redundant_connections, false); + ses.apply_settings(pack); + ses.async_add_torrent(params); + }, + [](lt::session&, lt::alert const*) {}, + [&expected](sim::simulation& sim, lt::session&) + { + sim::asio::io_context web_server(sim, make_address_v4("2.2.2.2")); + // listen on port 8080 + sim::http_server http(web_server, 8080); + + http.register_handler("/file1" + , [&expected](std::string method, std::string req + , std::map&) + { + expected = true; + char const* extra_headers[4] = { "Content-Range: bytes 0-0/1\r\n", "", "", ""}; + + return sim::send_response(206, "Partial Content", 1, extra_headers). + append("A"). + append(sim::send_response(408, "REQUEST TIMEOUT", 0)); + }); + + sim.run(); + } + ); + + TEST_CHECK(expected); +} + +// make sure the max_web_seed_connections limit is honored +TORRENT_TEST(web_seed_connection_limit) +{ + using namespace lt; + + file_storage fs; + fs.add_file("file1", 1); + lt::add_torrent_params params = ::create_torrent(fs); + params.url_seeds.push_back("http://2.2.2.1:8080/"); + params.url_seeds.push_back("http://2.2.2.2:8080/"); + params.url_seeds.push_back("http://2.2.2.3:8080/"); + params.url_seeds.push_back("http://2.2.2.4:8080/"); + + std::array expected = {}; + run_test( + [¶ms](lt::session& ses) + { + lt::settings_pack pack; + pack.set_int(settings_pack::max_web_seed_connections, 2); + ses.apply_settings(pack); + ses.async_add_torrent(params); + }, + [](lt::session&, lt::alert const*) {}, + [&expected](sim::simulation& sim, lt::session&) + { + using ios = sim::asio::io_context; + ios web_server1{sim, make_address_v4("2.2.2.1")}; + ios web_server2{sim, make_address_v4("2.2.2.2")}; + ios web_server3{sim, make_address_v4("2.2.2.3")}; + ios web_server4{sim, make_address_v4("2.2.2.4")}; + + // listen on port 8080 + using ws = sim::http_server; + ws http1{web_server1, 8080}; + ws http2{web_server2, 8080}; + ws http3{web_server3, 8080}; + ws http4{web_server4, 8080}; + + auto const handler = [&expected](std::string method, std::string req + , std::map&, int idx) + { + ++expected[idx]; + // deliberately avoid sending the content, to cause a hang + return sim::send_response(206, "Partial Content", 1); + }; + + using namespace std::placeholders; + http1.register_handler("/file1", std::bind(handler, _1, _2, _3, 0)); + http2.register_handler("/file1", std::bind(handler, _1, _2, _3, 1)); + http3.register_handler("/file1", std::bind(handler, _1, _2, _3, 2)); + http4.register_handler("/file1", std::bind(handler, _1, _2, _3, 3)); + + sim.run(); + }, + lt::seconds(15) + ); + + // make sure we only connected to 2 of the web seeds, since that's the limit + TEST_CHECK(std::accumulate(expected.begin(), expected.end(), 0) == 2); +} + +bool test_idna(char const* url, char const* redirect, bool allow_idna) +{ + using namespace lt; + file_storage fs; + fs.add_file("1", 0xc030); + lt::add_torrent_params params = ::create_torrent(fs); + params.url_seeds.emplace_back(url); + + bool seeding = false; + + error_code ignore; + remove("1", ignore); + + run_test( + [&](lt::session& ses) + { + settings_pack pack; + pack.set_bool(settings_pack::allow_idna, allow_idna); + ses.apply_settings(pack); + ses.async_add_torrent(params); + }, + [&](lt::session&, lt::alert const* alert) { + if (lt::alert_cast(alert)) + seeding = true; + }, + [&](sim::simulation& sim, lt::session&) + { + // http1 is the root web server that will just redirect requests to + // other servers + sim::asio::io_context web_server1(sim, make_address_v4("2.2.2.2")); + sim::http_server http1(web_server1, 8080); + // redirect file 1 and file 2 to the same servers + if (redirect) + http1.register_redirect("/1", redirect); + + // server for serving the content + sim::asio::io_context web_server2(sim, make_address_v4("3.3.3.3")); + sim::http_server http2(web_server2, 8080); + serve_content_for(http2, "/1", fs, file_index_t(0)); + + sim.run(); + } + ); + + return seeding; +} + +TORRENT_TEST(idna) +{ + // disallow IDNA hostnames + TEST_EQUAL(test_idna("http://3.server.com:8080", nullptr, false), true); + TEST_EQUAL(test_idna("http://3.xn--server-.com:8080", nullptr, false), false); + + // allow IDNA hostnames + TEST_EQUAL(test_idna("http://3.server.com:8080", nullptr, true), true); + TEST_EQUAL(test_idna("http://3.xn--server-.com:8080", nullptr, true), true); +} + +TORRENT_TEST(idna_redirect) +{ + // disallow IDNA hostnames + TEST_EQUAL(test_idna("http://2.server.com:8080", "http://3.server.com:8080/1", false), true); + TEST_EQUAL(test_idna("http://2.server.com:8080", "http://3.xn--server-.com:8080/1", false), false); + + TEST_EQUAL(test_idna("http://2.xn--server-.com:8080", "http://3.server.com:8080/1", false), false); + TEST_EQUAL(test_idna("http://2.xn--server-.com:8080", "http://3.xn--server-.com:8080/1", false), false); + + // allow IDNA hostnames + TEST_EQUAL(test_idna("http://2.server.com:8080", "http://3.server.com:8080/1", true), true); + TEST_EQUAL(test_idna("http://2.server.com:8080", "http://3.xn--server-.com:8080/1", true), true); + + TEST_EQUAL(test_idna("http://2.xn--server-.com:8080", "http://3.server.com:8080/1", true), true); + TEST_EQUAL(test_idna("http://2.xn--server-.com:8080", "http://3.xn--server-.com:8080/1", true), true); +} + +bool test_ssrf(char const* url, char const* redirect, bool enable_feature) +{ + using namespace lt; + file_storage fs; + fs.add_file("1", 0xc030); + lt::add_torrent_params params = ::create_torrent(fs); + params.url_seeds.emplace_back(url); + + bool seeding = false; + + error_code ignore; + remove("1", ignore); + + run_test( + [&](lt::session& ses) + { + settings_pack pack; + pack.set_bool(settings_pack::ssrf_mitigation, enable_feature); + ses.apply_settings(pack); + ses.async_add_torrent(params); + }, + [&](lt::session&, lt::alert const* alert) { + if (lt::alert_cast(alert)) + seeding = true; + }, + [&](sim::simulation& sim, lt::session&) + { + // http1 is the root web server that will just redirect requests to + // other servers + sim::asio::io_context web_server1(sim, make_address_v4("2.2.2.2")); + sim::http_server http1(web_server1, 8080); + // redirect file 1 and file 2 to the same servers + if (redirect) + http1.register_redirect("/1", redirect); + + // server for serving the content. This is on the local network + sim::asio::io_context web_server2(sim, make_address_v4("192.168.1.13")); + sim::http_server http2(web_server2, 8080); + serve_content_for(http2, "/1", fs, file_index_t(0)); + serve_content_for(http2, "/1?query_string=1", fs, file_index_t(0)); + + sim::asio::io_context web_server3(sim, make_address_v4("3.3.3.3")); + sim::http_server http3(web_server3, 8080); + serve_content_for(http3, "/1", fs, file_index_t(0)); + serve_content_for(http3, "/1?query_string=1", fs, file_index_t(0)); + + // a local network server that redurects + sim::asio::io_context web_server4(sim, make_address_v4("192.168.1.14")); + sim::http_server http4(web_server4, 8080); + if (redirect) + http4.register_redirect("/1", redirect); + + sim.run(); + } + ); + + return seeding; +} + +TORRENT_TEST(ssrf_mitigation) +{ + TEST_CHECK(test_ssrf("http://192.168.1.13:8080/1", nullptr, true)); + TEST_CHECK(test_ssrf("http://192.168.1.13:8080/1", nullptr, false)); + TEST_CHECK(test_ssrf("http://local-network.com:8080/1", nullptr, true)); + TEST_CHECK(test_ssrf("http://local-network.com:8080/1", nullptr, false)); + + TEST_CHECK(!test_ssrf("http://192.168.1.13:8080/1?query_string=1", nullptr, true)); + TEST_CHECK(test_ssrf("http://192.168.1.13:8080/1?query_string=1", nullptr, false)); + TEST_CHECK(!test_ssrf("http://local-network.com:8080/1?query_string=1", nullptr, true)); + TEST_CHECK(test_ssrf("http://local-network.com:8080/1?query_string=1", nullptr, false)); +} + +TORRENT_TEST(ssrf_mitigation_redirect) +{ + // All Global-IP -> Local-IP redirects are prevented by SSRF mitigation + TEST_CHECK(!test_ssrf("http://2.2.2.2:8080/1", "http://192.168.1.13:8080/1", true)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://192.168.1.13:8080/1", false)); + TEST_CHECK(!test_ssrf("http://2.2.2.2:8080/1", "http://local-network.com:8080/1", true)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://local-network.com:8080/1", false)); + TEST_CHECK(!test_ssrf("http://2.2.2.2:8080/1", "http://192.168.1.13:8080/1?query_string=1", true)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://192.168.1.13:8080/1?query_string=1", false)); + TEST_CHECK(!test_ssrf("http://2.2.2.2:8080/1", "http://local-network.com:8080/1?query_string=1", true)); + + + // Global-IP -> Global-IP is OK + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.server.com:8080/1", true)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.server.com:8080/1", false)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.server.com:8080/1", true)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.server.com:8080/1", false)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.server.com:8080/1?query_string=1", true)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.server.com:8080/1?query_string=1", false)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.server.com:8080/1?query_string=1", true)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.server.com:8080/1?query_string=1", false)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.3.3.3:8080/1", true)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.3.3.3:8080/1", false)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.3.3.3:8080/1", true)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.3.3.3:8080/1", false)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.3.3.3:8080/1?query_string=1", true)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.3.3.3:8080/1?query_string=1", false)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.3.3.3:8080/1?query_string=1", true)); + TEST_CHECK(test_ssrf("http://2.2.2.2:8080/1", "http://3.3.3.3:8080/1?query_string=1", false)); + + // Local-IP -> Local-IP are OK, with the normal query string restrictions + TEST_CHECK(test_ssrf("http://192.168.1.14:8080/1", "http://192.168.1.13:8080/1", true)); + TEST_CHECK(test_ssrf("http://192.168.1.14:8080/1", "http://192.168.1.13:8080/1", false)); + TEST_CHECK(test_ssrf("http://192.168.1.14:8080/1", "http://local-network.com:8080/1", true)); + TEST_CHECK(test_ssrf("http://192.168.1.14:8080/1", "http://local-network.com:8080/1", false)); + TEST_CHECK(!test_ssrf("http://192.168.1.14:8080/1", "http://192.168.1.13:8080/1?query_string=1", true)); + TEST_CHECK(test_ssrf("http://192.168.1.14:8080/1", "http://192.168.1.13:8080/1?query_string=1", false)); + TEST_CHECK(!test_ssrf("http://192.168.1.14:8080/1", "http://local-network.com:8080/1?query_string=1", true)); +} diff --git a/simulation/transfer_sim.cpp b/simulation/transfer_sim.cpp new file mode 100644 index 0000000..8148954 --- /dev/null +++ b/simulation/transfer_sim.cpp @@ -0,0 +1,53 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +You may use, distribute and modify this code under the terms of the BSD license, +see LICENSE file. +*/ + +#include "libtorrent/settings_pack.hpp" + +#include "transfer_sim.hpp" + +using lt::settings_pack; + +void no_init(lt::session& ses0, lt::session& ses1) {} + +record_finished_pieces::record_finished_pieces(std::set& p) + : m_passed(&p) +{} + +void record_finished_pieces::operator()(lt::session&, lt::alert const* a) const +{ + if (auto const* pf = lt::alert_cast(a)) + m_passed->insert(pf->piece_index); +} + +expect_seed::expect_seed(bool e) : m_expect(e) {} +void expect_seed::operator()(std::shared_ptr ses[2]) const +{ + TEST_EQUAL(is_seed(*ses[0]), m_expect); +} + +int blocks_per_piece(test_transfer_flags_t const flags) +{ + if (flags & tx::small_pieces) return 1; + if (flags & tx::large_pieces) return 4; + return 2; +} + +int num_pieces(test_transfer_flags_t const flags) +{ + if (flags & tx::multiple_files) + { + // since v1 torrents don't pad files by default, there will be fewer + // pieces on those torrents + if (flags & tx::v1_only) + return 31; + else + return 33; + } + return 11; +} diff --git a/simulation/transfer_sim.hpp b/simulation/transfer_sim.hpp new file mode 100644 index 0000000..1abb983 --- /dev/null +++ b/simulation/transfer_sim.hpp @@ -0,0 +1,223 @@ +/* + +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_TRANSFER_SIM_HPP +#define TORRENT_TRANSFER_SIM_HPP + +#include +#include +#include + +#include "simulator/simulator.hpp" +#include "simulator/socks_server.hpp" +#include "simulator/utils.hpp" + +#include "libtorrent/session.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/random.hpp" + +#include "test.hpp" +#include "create_torrent.hpp" +#include "settings.hpp" +#include "setup_swarm.hpp" +#include "utils.hpp" +#include "test_utils.hpp" +#include "setup_transfer.hpp" // for addr() +#include "disk_io.hpp" + +template +void run_test( + Setup setup + , HandleAlerts on_alert + , Test test + , test_transfer_flags_t flags = {} + , test_disk const downloader_disk_constructor = test_disk() + , test_disk const seed_disk_constructor = test_disk() + , lt::seconds const timeout = lt::seconds(60) + ) +{ + using lt::settings_pack; + using lt::address; + using lt::alert_cast; + + const bool use_ipv6 = bool(flags & tx::ipv6); + + char const* peer0_ip[2] = { "50.0.0.1", "feed:face:baad:f00d::1" }; + char const* peer1_ip[2] = { "50.0.0.2", "feed:face:baad:f00d::2" }; + + address peer0 = addr(peer0_ip[use_ipv6]); + address peer1 = addr(peer1_ip[use_ipv6]); + address proxy = (flags & tx::ipv6) ? addr("2001::2") : addr("50.50.50.50"); + + // setup the simulation + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + sim::asio::io_context ios0 { sim, peer0 }; + sim::asio::io_context ios1 { sim, peer1 }; + + lt::session_proxy zombie[2]; + + sim::asio::io_context proxy_ios{sim, proxy }; + sim::socks_server socks4(proxy_ios, 4444, 4); + sim::socks_server socks5(proxy_ios, 5555, 5); + socks5.bind_start_port(3000); + + lt::session_params params; + // setup settings pack to use for the session (customization point) + lt::settings_pack& pack = params.settings; + pack = settings(); + pack.set_bool(settings_pack::disable_hash_checks, false); + + // disable utp by default + pack.set_bool(settings_pack::enable_outgoing_utp, false); + pack.set_bool(settings_pack::enable_incoming_utp, false); + + // disable encryption by default + 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_plaintext); + + pack.set_str(settings_pack::listen_interfaces, make_ep_string(peer0_ip[use_ipv6], use_ipv6, "6881")); + + // create session + std::shared_ptr ses[2]; + + // session 0 is a downloader, session 1 is a seed + + params.disk_io_constructor = downloader_disk_constructor; + ses[0] = std::make_shared(params, ios0); + + pack.set_str(settings_pack::listen_interfaces, make_ep_string(peer1_ip[use_ipv6], use_ipv6, "6881")); + + params.disk_io_constructor = seed_disk_constructor.set_files(existing_files_mode::full_valid); + ses[1] = std::make_shared(params, ios1); + + setup(*ses[0], *ses[1]); + + // only monitor alerts for session 0 (the downloader) + print_alerts(*ses[0], [=](lt::session& ses, lt::alert const* a) { + if (auto ta = alert_cast(a)) + { + if (flags & tx::connect_proxy) + ta->handle.connect_peer(lt::tcp::endpoint(proxy, 3000)); + else + ta->handle.connect_peer(lt::tcp::endpoint(peer1, 6881)); + } + on_alert(ses, a); + }, 0); + + print_alerts(*ses[1], [](lt::session&, lt::alert const*){}, 1); + + lt::add_torrent_params atp = ::create_test_torrent(10 + , (flags & tx::v2_only) ? lt::create_torrent::v2_only + : (flags & tx::v1_only) ? lt::create_torrent::v1_only + : lt::create_flags_t{} + , (flags & tx::small_pieces) ? 1 : (flags & tx::large_pieces) ? 4 : 2 + , (flags & tx::multiple_files) ? 3 : 1 + ); + atp.flags &= ~lt::torrent_flags::auto_managed; + atp.flags &= ~lt::torrent_flags::paused; + + ses[1]->async_add_torrent(atp); + auto torrent = atp.ti; + + atp.save_path = save_path(0); + if (flags & tx::magnet_download) + { + atp.info_hashes = atp.ti->info_hashes(); + atp.ti.reset(); + } + ses[0]->async_add_torrent(atp); + + sim::timer t(sim, timeout, [&](boost::system::error_code const&) + { + auto h = ses[0]->get_torrents(); + auto ti = h[0].torrent_file_with_hashes(); + + // if we're a seed, we should definitely have the torrent info. If we're + // note a seed, we may still have the torrent_info in case it's a v1 + // torrent + if (is_seed(*ses[0])) TEST_CHECK(ti); + + if (ti) + { + if (ti->v2()) + TEST_EQUAL(ti->v2_piece_hashes_verified(), true); + + auto downloaded = serialize(*ti); + auto added = serialize(*torrent); + TEST_CHECK(downloaded == added); + } + + test(ses); + + // shut down + int idx = 0; + for (auto& s : ses) + { + zombie[idx++] = s->abort(); + s.reset(); + } + }); + + sim.run(); +} + +void no_init(lt::session& ses0, lt::session& ses1); + +struct record_finished_pieces +{ + record_finished_pieces(std::set& p); + void operator()(lt::session&, lt::alert const* a) const; + + std::set* m_passed; +}; + +struct expect_seed +{ + expect_seed(bool e); + void operator()(std::shared_ptr ses[2]) const; + bool m_expect; +}; + +int blocks_per_piece(test_transfer_flags_t const flags); +int num_pieces(test_transfer_flags_t const flags); + +#endif diff --git a/simulation/utils.cpp b/simulation/utils.cpp new file mode 100644 index 0000000..e6496b0 --- /dev/null +++ b/simulation/utils.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 "utils.hpp" +#include "test.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/random.hpp" +#include "setup_swarm.hpp" +#include "setup_transfer.hpp" // for addr() + +using namespace lt; + +#ifdef _MSC_VER +namespace libtorrent { +namespace aux { + // see test_error_handling.cpp for a description of this variable + // TODO: in C++17, make this inline + thread_local int g_must_not_fail = 0; +} +} +#endif + +std::string make_ep_string(char const* address, bool const is_v6 + , char const* port) +{ + std::string ret; + if (is_v6) ret += '['; + ret += address; + if (is_v6) ret += ']'; + ret += ':'; + ret += port; + return ret; +} + +void utp_only(lt::session& ses) +{ + settings_pack p; + utp_only(p); + ses.apply_settings(p); +} + +void enable_enc(lt::session& ses) +{ + settings_pack p; + enable_enc(p); + ses.apply_settings(p); +} + +void filter_ips(lt::session& ses) +{ + ip_filter filter; + filter.add_rule(make_address_v4("50.0.0.1") + , make_address_v4("50.0.0.2"), ip_filter::blocked); + ses.set_ip_filter(filter); +} + +void utp_only(lt::settings_pack& p) +{ + using namespace lt; + p.set_bool(settings_pack::enable_outgoing_tcp, false); + p.set_bool(settings_pack::enable_incoming_tcp, false); + p.set_bool(settings_pack::enable_outgoing_utp, true); + p.set_bool(settings_pack::enable_incoming_utp, true); +} + +void enable_enc(lt::settings_pack& p) +{ + using namespace lt; + p.set_bool(settings_pack::prefer_rc4, true); + p.set_int(settings_pack::in_enc_policy, settings_pack::pe_forced); + p.set_int(settings_pack::out_enc_policy, settings_pack::pe_forced); + p.set_int(settings_pack::allowed_enc_level, settings_pack::pe_both); +} + +std::string save_path(int swarm_id, int idx) +{ + char path[200]; + std::snprintf(path, sizeof(path), "swarm-%04d-peer-%02d" + , swarm_id, idx); + return path; +} + +void add_extra_peers(lt::session& ses) +{ + auto handles = ses.get_torrents(); + TEST_EQUAL(handles.size(), 1); + auto h = handles[0]; + + for (int i = 0; i < 30; ++i) + { + char ep[30]; + std::snprintf(ep, sizeof(ep), "60.0.0.%d", i + 1); + h.connect_peer(lt::tcp::endpoint(addr(ep), 6881)); + } +} + +lt::torrent_status get_status(lt::session& ses) +{ + auto handles = ses.get_torrents(); + TEST_EQUAL(handles.size(), 1); + if (handles.empty()) return lt::torrent_status(); + auto h = handles[0]; + return h.status(); +} + +bool has_metadata(lt::session& ses) +{ + auto handles = ses.get_torrents(); + TEST_EQUAL(handles.size(), 1); + if (handles.empty()) return false; + auto h = handles[0]; + return h.status().has_metadata; +} + +bool is_seed(lt::session& ses) +{ + auto handles = ses.get_torrents(); + TEST_EQUAL(handles.size(), 1); + if (handles.empty()) return false; + auto h = handles[0]; + return h.status().is_seeding; +} + +bool is_finished(lt::session& ses) +{ + auto handles = ses.get_torrents(); + TEST_EQUAL(handles.size(), 1); + if (handles.empty()) return false; + auto h = handles[0]; + return h.status().is_finished; +} + +int completed_pieces(lt::session& ses) +{ + auto handles = ses.get_torrents(); + TEST_EQUAL(handles.size(), 1); + if (handles.empty()) return 0; + auto h = handles[0]; + return h.status().num_pieces; +} + + +void set_proxy(lt::session& ses, int proxy_type, test_transfer_flags_t const flags + , bool const proxy_peers) +{ + // apply the proxy settings to session 0 + settings_pack p; + p.set_int(settings_pack::proxy_type, proxy_type); + if (proxy_type == settings_pack::socks4) + p.set_int(settings_pack::proxy_port, 4444); + else + p.set_int(settings_pack::proxy_port, 5555); + if (flags & tx::ipv6) + p.set_str(settings_pack::proxy_hostname, "2001::2"); + else + p.set_str(settings_pack::proxy_hostname, "50.50.50.50"); + p.set_bool(settings_pack::proxy_hostnames, true); + p.set_bool(settings_pack::proxy_peer_connections, bool(flags & tx::proxy_peers)); + p.set_bool(settings_pack::proxy_tracker_connections, proxy_peers); + p.set_bool(settings_pack::socks5_udp_send_local_ep, true); + + ses.apply_settings(p); +} + +void print_alerts(lt::session& ses + , std::function on_alert + , int const idx) +{ + lt::time_point start_time = lt::clock_type::now(); + + static std::vector alerts; + + ses.set_alert_notify([&ses,start_time,on_alert,idx] { + post(ses.get_context(), [&ses,start_time,on_alert,idx] { + + try { + alerts.clear(); + ses.pop_alerts(&alerts); + + for (lt::alert const* a : alerts) + { + auto const ts = a->timestamp() - start_time; + std::printf("\x1b[%dm%3d.%03d %s\n" + , idx == 0 ? 0 : 34 + , int(lt::duration_cast(ts).count()) + , int(lt::duration_cast(ts).count() % 1000) + , a->message().c_str()); + // call the user handler + on_alert(ses, a); + } + } catch (std::exception const& e) { + std::printf("print alerts: ERROR failed with exception: %s" + , e.what()); + } catch (...) { + std::printf("print alerts: ERROR failed with (unknown) exception"); + } + } ); } ); +} + +std::unique_ptr make_io_context(sim::simulation& sim, int i) +{ + char ep[30]; + std::snprintf(ep, sizeof(ep), "50.0.%d.%d", (i + 1) >> 8, (i + 1) & 0xff); + return std::make_unique(sim, lt::make_address_v4(ep)); +} + +sha256_hash rand_sha256() +{ + sha256_hash ret; + aux::random_bytes(ret); + return ret; +} + +sha1_hash rand_sha1() +{ + sha1_hash ret; + aux::random_bytes(ret); + return ret; +} + diff --git a/simulation/utils.hpp b/simulation/utils.hpp new file mode 100644 index 0000000..e99ccdf --- /dev/null +++ b/simulation/utils.hpp @@ -0,0 +1,104 @@ +/* + +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_UTILS_HPP_INCLUDED +#define TORRENT_UTILS_HPP_INCLUDED + +#include +#include +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/settings_pack.hpp" +#include "simulator/simulator.hpp" + +namespace libtorrent +{ + struct session; + struct alert; +} + +std::string make_ep_string(char const* address, bool const is_v6, char const* port); + +// adds an IP filter to disallow 50.0.0.1 and 50.0.0.2 +void filter_ips(lt::session& ses); + +bool has_metadata(lt::session& ses); +bool is_seed(lt::session& ses); +bool is_finished(lt::session& ses); +int completed_pieces(lt::session& ses); +void add_extra_peers(lt::session& ses); +lt::torrent_status get_status(lt::session& ses); + +// disable TCP and enable uTP +void utp_only(lt::session& ses); +void utp_only(lt::settings_pack& pack); + +// force encrypted connections +void enable_enc(lt::session& ses); +void enable_enc(lt::settings_pack& pack); + +std::string save_path(int swarm_id, int idx); + +std::unique_ptr make_io_context( + sim::simulation& sim, int i); + +using lt::operator""_bit; + +using test_transfer_flags_t = libtorrent::flags::bitfield_flag; + +namespace tx { +constexpr test_transfer_flags_t ipv6 = 0_bit; +constexpr test_transfer_flags_t v1_only = 1_bit; +constexpr test_transfer_flags_t v2_only = 2_bit; +constexpr test_transfer_flags_t magnet_download = 3_bit; +constexpr test_transfer_flags_t proxy_peers = 4_bit; +constexpr test_transfer_flags_t small_pieces = 5_bit; +constexpr test_transfer_flags_t large_pieces = 6_bit; +constexpr test_transfer_flags_t multiple_files = 7_bit; +constexpr test_transfer_flags_t connect_proxy = 8_bit; +} + +void set_proxy(lt::session& ses, int proxy_type + , test_transfer_flags_t flags = tx::proxy_peers + , bool proxy_peers = true); + +void print_alerts(lt::session& ses + , std::function on_alert + = [](lt::session&, lt::alert const*) {}, int idx = 0); + +lt::sha256_hash rand_sha256(); +lt::sha1_hash rand_sha1(); + +#endif + diff --git a/src/add_torrent_params.cpp b/src/add_torrent_params.cpp new file mode 100644 index 0000000..423da9d --- /dev/null +++ b/src/add_torrent_params.cpp @@ -0,0 +1,99 @@ +/* + +Copyright (c) 2016-2020, Arvid Norberg +Copyright (c) 2017, 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() = default; + add_torrent_params::~add_torrent_params() = default; + add_torrent_params::add_torrent_params(add_torrent_params&&) noexcept = default; + add_torrent_params& add_torrent_params::operator=(add_torrent_params&&) & = 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..8692633 --- /dev/null +++ b/src/alert.cpp @@ -0,0 +1,3175 @@ +/* + +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2009-2020, Arvid Norberg +Copyright (c) 2014-2018, Steven Siloti +Copyright (c) 2015, Thomas +Copyright (c) 2015-2018, Alden Torres +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2017, Antoine Dahan +Copyright (c) 2019, Amir Abrams +Copyright (c) 2020, Fonic +Copyright (c) 2020, Viktor Elofsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" // for is_v4 + +#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; +#if TORRENT_ABI_VERSION <= 2 + constexpr alert_category_t alert::stats_notification; +#endif + 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; + + 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 + { + if (t->info_hash().has_v2()) + m_name_idx = alloc.copy_string(aux::to_hex(t->info_hash().v2)); + else + m_name_idx = alloc.copy_string(aux::to_hex(t->info_hash().v1)); + } + } + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + if (!handle.is_valid()) return " - "; + return torrent_name(); +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " peer [ " + print_endpoint(endpoint) + + " client: " + aux::identify_client_impl(pid) + " ]"; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " (" + tracker_url() + ")" + + "[" + print_endpoint(local_endpoint) + "]"; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + file_renamed_alert::file_renamed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, string_view n, string_view old, file_index_t const idx) + : torrent_alert(alloc, h) + , index(idx) + , m_name_idx(alloc.copy_string(n)) + , m_old_name_idx(alloc.copy_string(old)) +#if TORRENT_ABI_VERSION == 1 + , name(n) +#endif + {} + + char const* file_renamed_alert::new_name() const + { + return m_alloc.get().ptr(m_name_idx); + } + + char const* file_renamed_alert::old_name() const + { + return m_alloc.get().ptr(m_old_name_idx); + } + + std::string file_renamed_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + std::string ret { torrent_alert::message() }; + char msg[200]; + std::snprintf(msg, sizeof(msg), ": file %d renamed from \"" + , static_cast(index)); + ret.append(msg); + ret.append(old_name()); + ret.append("\" to \""); + ret.append(new_name()); + ret.append("\""); + return ret; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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) + { +#ifdef TORRENT_DISABLE_ALERT_MSG + TORRENT_UNUSED(i); + return ""; +#else + 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", + "", + "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]; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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]; +#endif + } + + tracker_error_alert::tracker_error_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep, int times + , string_view u, operation_t const operation, error_code const& e + , string_view m) + : tracker_alert(alloc, h, ep, u) + , times_in_row(times) + , error(e) + , op(operation) + , 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::failure_reason() const + { + return m_alloc.get().ptr(m_msg_idx); + } + + std::string tracker_error_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return tracker_alert::message() + " warning: " + warning_message(); +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char ret[400]; + std::snprintf(ret, sizeof(ret), "%s scrape reply: %d %d" + , tracker_alert::message().c_str(), incomplete, complete); + return ret; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return tracker_alert::message() + " scrape failed: " + error_message(); +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char ret[400]; + std::snprintf(ret, sizeof(ret), "%s received peers: %d" + , tracker_alert::message().c_str(), num_peers); + return ret; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char ret[400]; + std::snprintf(ret, sizeof(ret), "%s received DHT peers: %d" + , tracker_alert::message().c_str(), num_peers); + return ret; +#endif + } + + tracker_announce_alert::tracker_announce_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep, string_view u + , event_t const e) + : tracker_alert(alloc, h, ep, u) + , event(e) + { + TORRENT_ASSERT(!u.empty()); + } + + std::string tracker_announce_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + static const char* const event_str[] = {"none", "completed", "started", "stopped", "paused"}; + return tracker_alert::message() + " sending announce (" + event_str[static_cast(event)] + ")"; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return peer_alert::message() + " banned peer"; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return peer_alert::message() + " peer unsnubbed"; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return peer_alert::message() + " peer snubbed"; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + torrent_finished_alert::torrent_finished_alert(aux::stack_allocator& alloc + , torrent_handle h) + : torrent_alert(alloc, h) + {} + + std::string torrent_finished_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " torrent finished downloading"; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char ret[200]; + std::snprintf(ret, sizeof(ret), "%s piece: %d finished downloading" + , torrent_alert::message().c_str(), static_cast(piece_index)); + return ret; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + storage_moved_alert::storage_moved_alert(aux::stack_allocator& alloc + , torrent_handle const& h, string_view p, string_view old) + : torrent_alert(alloc, h) + , m_path_idx(alloc.copy_string(p)) + , m_old_path_idx(alloc.copy_string(old)) +#if TORRENT_ABI_VERSION == 1 + , path(p) +#endif + {} + + std::string storage_moved_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " moved storage from \"" + + old_path() + "\" to: \"" + storage_path() + "\""; +#endif + } + + char const* storage_moved_alert::storage_path() const + { + return m_alloc.get().ptr(m_path_idx); + } + + char const* storage_moved_alert::old_path() const + { + return m_alloc.get().ptr(m_old_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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " storage move failed. " + + operation_name(op) + " (" + file_path() + "): " + + convert_from_native(error.message()); +#endif + } + + torrent_deleted_alert::torrent_deleted_alert(aux::stack_allocator& alloc + , torrent_handle const& h, info_hash_t const& ih) + : torrent_alert(alloc, h) + , info_hashes(ih) + { +#if TORRENT_ABI_VERSION < 3 + info_hash = info_hashes.get_best(); +#endif + } + + std::string torrent_deleted_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " deleted"; +#endif + } + + torrent_delete_failed_alert::torrent_delete_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e, info_hash_t const& ih) + : torrent_alert(alloc, h) + , error(e) + , info_hashes(ih) +#if TORRENT_ABI_VERSION == 1 + , msg(convert_from_native(error.message())) +#endif + { +#if TORRENT_ABI_VERSION < 3 + info_hash = info_hashes.get_best(); +#endif + } + + std::string torrent_delete_failed_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " torrent deletion failed: " + + convert_from_native(error.message()); +#endif + } + + 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 + { +#if TORRENT_ABI_VERSION < 3 + params.info_hash = params.info_hashes.get_best(); +#endif + } + + std::string save_resume_data_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " resume data generated"; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " resume data was not generated: " + + convert_from_native(error.message()); +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " paused"; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " resumed"; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " checked"; +#endif + } + +namespace { + +#ifndef TORRENT_DISABLE_ALERT_MSG + char const* const nat_type_str[] = {"NAT-PMP", "UPnP"}; + + char const* const protocol_str[] = {"none", "TCP", "UDP"}; +#endif + +#if TORRENT_ABI_VERSION == 1 + int sock_type_idx(socket_type_t type) + { + // these numbers are the deprecated enum values in + // listen_succeeded_alert and listen_failed_alert + static aux::array const mapping{{ + 0, // tcp + 4, // socks5, + 0, // http, + 2, // utp, + 3, // i2p, + 1, // tcp_ssl + 4, // socks5_ssl, + 1, // http_ssl, + 5 // utp_ssl, + }}; + return mapping[type]; + } + + 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 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + 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; + case o::file_seek: return -1; + case o::timer: return -1; + case o::file_mmap: return -1; + case o::file_truncate: return -1; + } + return -1; + } +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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) + , socket_type_name(socket_type) + , convert_from_native(error.message()).c_str()); + return ret; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " invalid metadata received"; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " metadata successfully received"; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return "UDP error: " + convert_from_native(error.message()) + + " from: " + endpoint.address().to_string() + + " op: " + operation_name(operation); +#endif + } + + external_ip_alert::external_ip_alert(aux::stack_allocator& + , address const& ip) + : external_address(ip) + {} + + std::string external_ip_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return "external IP received: " + external_address.to_string(); +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char ret[200]; + std::snprintf(ret, sizeof(ret), "successfully listening on [%s] %s" + , socket_type_name(socket_type), print_endpoint(address, port).c_str()); + return ret; +#endif + } + + portmap_error_alert::portmap_error_alert(aux::stack_allocator& + , port_mapping_t const i, portmap_transport const t, error_code const& e + , address const& local) + : mapping(i) + , map_transport(t) + , local_address(local) + , 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return std::string("could not map port using ") + + nat_type_str[static_cast(map_transport)] + + "[" + local_address.to_string() + "]: " + + convert_from_native(error.message()); +#endif + } + + portmap_alert::portmap_alert(aux::stack_allocator&, port_mapping_t const i + , int const port, portmap_transport const t, portmap_protocol const proto + , address const& local) + : mapping(i) + , external_port(port) + , map_protocol(proto) + , map_transport(t) + , local_address(local) +#if TORRENT_ABI_VERSION == 1 + , protocol(static_cast(proto)) + , map_type(static_cast(t)) +#endif + {} + + std::string portmap_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char ret[200]; + std::snprintf(ret, sizeof(ret), "successfully mapped port using %s. local: %s external port: %s/%d" + , nat_type_str[static_cast(map_transport)] + , local_address.to_string().c_str() + , protocol_str[static_cast(map_protocol)], external_port); + return ret; +#endif + } + + portmap_log_alert::portmap_log_alert(aux::stack_allocator& alloc + , portmap_transport const t, const char* m, address const& local) + : map_transport(t) + , local_address(local) + , 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char ret[1024]; + std::snprintf(ret, sizeof(ret), "%s [%s]: %s" + , nat_type_str[static_cast(map_transport)] + , local_address.to_string().c_str() + , log_message()); + return ret; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " fast resume rejected. " + + operation_name(op) + "(" + file_path() + "): " + + convert_from_native(error.message()); +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char ret[600]; + static char const* const reason_str[] = + { + "ip_filter", + "port_filter", + "i2p_mixed", + "privileged_ports", + "utp_disabled", + "tcp_disabled", + "invalid_local_interface", + "ssrf_mitigation" + }; + + std::snprintf(ret, sizeof(ret), "%s: blocked peer [%s]" + , peer_alert::message().c_str(), reason_str[reason]); + return ret; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char msg[200]; + std::snprintf(msg, sizeof(msg), "incoming dht announce: %s:%d (%s)" + , ip.to_string().c_str(), port, aux::to_hex(info_hash).c_str()); + return msg; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char msg[200]; + std::snprintf(msg, sizeof(msg), "incoming dht get_peers: %s", aux::to_hex(info_hash).c_str()); + return msg; +#endif + } + +#if TORRENT_ABI_VERSION <= 2 +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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } +#endif // TORRENT_ABI_VERSION + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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 + } +#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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char msg[200]; + std::snprintf(msg, sizeof(msg), "%s: received peer from local service discovery" + , peer_alert::message().c_str()); + return msg; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return std::string("trackerid received: ") + tracker_id(); +#endif + } + + dht_bootstrap_alert::dht_bootstrap_alert(aux::stack_allocator&) + {} + + std::string dht_bootstrap_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return "DHT bootstrap complete"; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " added"; +#endif + } +#endif + + torrent_removed_alert::torrent_removed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, info_hash_t const& ih, client_data_t u) + : torrent_alert(alloc, h) + , info_hashes(ih) + , userdata(u) + { +#if TORRENT_ABI_VERSION < 3 + info_hash = info_hashes.get_best(); +#endif + } + + std::string torrent_removed_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " removed"; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " needs SSL certificate"; +#endif + } + + incoming_connection_alert::incoming_connection_alert(aux::stack_allocator& + , socket_type_t 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char msg[600]; + std::snprintf(msg, sizeof(msg), "incoming connection from %s (%s)" + , print_endpoint(endpoint).c_str(), socket_type_name(socket_type)); + return msg; +#endif + } + + peer_connect_alert::peer_connect_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, socket_type_t const type, direction_t const dir) + : peer_alert(alloc, h, ep, peer_id) + , direction(dir) + , socket_type(type) + {} + + std::string peer_connect_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char msg[600]; + char const* direction_str = direction == direction_t::in ? "incoming" : "outgoing"; + std::snprintf(msg, sizeof(msg), "%s %s connection to peer (%s)" + , peer_alert::message().c_str(), direction_str, socket_type_name(socket_type)); + return msg; +#endif + } + + add_torrent_alert::add_torrent_alert(aux::stack_allocator& alloc, torrent_handle const& h + , add_torrent_params p, error_code const& ec) + : torrent_alert(alloc, h) + , params(std::move(p)) + , error(ec) + { +#if TORRENT_ABI_VERSION < 3 + params.info_hash = params.info_hashes.get_best(); +#endif + } + + std::string add_torrent_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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_hashes.get_best(), 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; +#endif + } + + state_update_alert::state_update_alert(aux::stack_allocator& + , std::vector st) + : status(std::move(st)) + {} + + std::string state_update_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char msg[600]; + std::snprintf(msg, sizeof(msg), "state updates for %d torrents", int(status.size())); + return msg; +#endif + } + +#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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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 + } +#endif + + char const* operation_name(operation_t const op) + { +#ifdef TORRENT_DISABLE_ALERT_MSG + TORRENT_UNUSED(op); + return ""; +#else + 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", + "file_seek", + "timer", + "file_mmap", + "file_truncate", + }; + + int const idx = static_cast(op); + if (idx < 0 || idx >= int(sizeof(names) / sizeof(names[0]))) + return "unknown operation"; + + return names[idx]; +#endif + } + +#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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#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_, socket_type_t const 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char buf[600]; + std::snprintf(buf, sizeof(buf), "%s disconnecting (%s) [%s] [%s]: %s (reason: %d)" + , peer_alert::message().c_str() + , socket_type_name(socket_type) + , operation_name(op), error.category().name() + , convert_from_native(error.message()).c_str() + , int(reason)); + return buf; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + dht_immutable_item_alert::dht_immutable_item_alert(aux::stack_allocator& + , sha1_hash const& t, entry i) + : target(t), item(std::move(i)) + {} + + std::string dht_immutable_item_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + // 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 i + , bool a) + : key(k), signature(sig), seq(sequence), salt(s), item(std::move(i)), authoritative(a) + {} + + std::string dht_mutable_item_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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 complete (success=%d hash=%s)" + , num_success + , aux::to_hex(target).c_str()); + return msg; +#endif + } + + i2p_alert::i2p_alert(aux::stack_allocator&, error_code const& ec) + : error(ec) + {} + + std::string i2p_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char msg[600]; + std::snprintf(msg, sizeof(msg), "i2p_error: [%s] %s" + , error.category().name(), convert_from_native(error.message()).c_str()); + return msg; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return log_message(); +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + ": " + log_message(); +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + static char const* const mode[] = + { "<==", "==>", "<<<", ">>>", "***" }; + return peer_alert::message() + " [" + print_endpoint(endpoint) + "] " + + mode[direction] + " " + event_type + " [ " + log_message() + " ]"; +#endif + } + + lsd_error_alert::lsd_error_alert(aux::stack_allocator&, error_code const& ec + , address const& local) + : alert() + , local_address(local) + , error(ec) + {} + + std::string lsd_error_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return "Local Service Discovery startup error [" + local_address.to_string() + "]: " + + convert_from_native(error.message()); +#endif + } + +#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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + , sha1_hash id, udp::endpoint ep) + : alert() + , active_requests(std::move(requests)) + , routing_table(std::move(table)) + , nid(id) + , local_endpoint(ep) + {} + + std::string dht_stats_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + char buf[2048]; + std::snprintf(buf, sizeof(buf), "DHT stats: (%s) reqs: %d buckets: %d" + , aux::to_hex(nid).c_str() + , int(active_requests.size()) + , int(routing_table.size())); + return buf; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " url seed (" + + server_url() + ") failed: " + convert_from_native(error.message()); +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " " + + operation_name(op) + " (" + filename() + + ") error: " + convert_from_native(error.message()); +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 (aux::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 (aux::is_v4(endp)) + aux::write_endpoint(endp, v4_ptr); + else + aux::write_endpoint(endp, v6_ptr); + } + } + + std::string dht_get_peers_reply_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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(aux::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(aux::read_v6_endpoint(v6_ptr)); + + return std::move(peers); + } + + dht_direct_response_alert::dht_direct_response_alert( + aux::stack_allocator& alloc, client_data_t 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 + , client_data_t 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + auto 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + +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 (aux::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 (aux::is_v4(endp)) + { + aux::write_string(n.first.to_string(), v4_ptr); + aux::write_endpoint(endp, v4_ptr); + } + else + { + aux::write_string(n.first.to_string(), v6_ptr); + aux::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, aux::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, aux::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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + dht_sample_infohashes_alert::dht_sample_infohashes_alert(aux::stack_allocator& alloc + , sha1_hash const& nid + , udp::endpoint const& endp + , time_duration _interval + , int _num + , std::vector const& samples + , std::vector> const& nodes) + : node_id(nid) + , 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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", + "file_prio", "oversized_file" + }}; + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + 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 + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + 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; +#endif + } + + file_prio_alert::file_prio_alert(aux::stack_allocator& a, torrent_handle h) + : torrent_alert(a, std::move(h)) + {} + + std::string file_prio_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " file priorities updated"; +#endif + } + + oversized_file_alert::oversized_file_alert(aux::stack_allocator& a, torrent_handle h) + : torrent_alert(a, std::move(h)) + {} + + std::string oversized_file_alert::message() const + { +#ifdef TORRENT_DISABLE_ALERT_MSG + return {}; +#else + return torrent_alert::message() + " has an oversized file"; +#endif + } + + // 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; +#if TORRENT_ABI_VERSION <= 2 + constexpr alert_category_t stats_alert::static_category; +#endif + 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; + constexpr alert_category_t file_prio_alert::static_category; + constexpr alert_category_t oversized_file_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; +#endif + +} // namespace libtorrent diff --git a/src/alert_manager.cpp b/src/alert_manager.cpp new file mode 100644 index 0000000..3836a3f --- /dev/null +++ b/src/alert_manager.cpp @@ -0,0 +1,148 @@ +/* + +Copyright (c) 2003-2013, Daniel Wallin +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016, 2020, 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_/alert_manager.hpp" +#include "libtorrent/alert_types.hpp" + +#ifndef TORRENT_DISABLE_EXTENSIONS +#include "libtorrent/extensions.hpp" +#include // for shared_ptr +#endif + +namespace libtorrent { +namespace aux { + + 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..7bf17d6 --- /dev/null +++ b/src/announce_entry.cpp @@ -0,0 +1,289 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2016-2017, 2020, Alden Torres +Copyright (c) 2017-2018, 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/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" +#include "libtorrent/aux_/announce_entry.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 }; + } + +TORRENT_VERSION_NAMESPACE_2 + + announce_infohash::announce_infohash() + : fails(0) + , updating(false) + , start_sent(false) + , complete_sent(false) + , triggered_manually(false) + {} + + 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; + + announce_endpoint::announce_endpoint() = default; + +#if TORRENT_ABI_VERSION <= 2 + void announce_infohash::reset() + { + start_sent = false; + next_announce = time_point32::min(); + min_announce = time_point32::min(); + } + + void announce_infohash::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 + )); + TORRENT_ASSERT(delay <= tracker_retry_delay_max); + if (!is_working()) next_announce = aux::time_now32() + delay; + updating = false; + } + + bool announce_infohash::can_announce(time_point now, bool is_seed, std::uint8_t fail_limit) const + { + TORRENT_ASSERT(next_announce <= now + tracker_retry_delay_max); + // 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_endpoint::reset() + { + for (auto& ih : info_hashes) + ih.reset(); + + start_sent = false; + next_announce = time_point32::min(); + min_announce = time_point32::min(); + } + + void announce_entry::reset() + { + for (auto& aep : endpoints) + aep.reset(); + } + + bool announce_endpoint::can_announce(time_point now, bool is_seed, std::uint8_t fail_limit) const + { + return std::any_of(std::begin(info_hashes), std::end(info_hashes) + , [&](announce_infohash const& ih) { return ih.can_announce(now, is_seed, fail_limit); }); + } + + bool announce_endpoint::is_working() const + { + return std::any_of(std::begin(info_hashes), std::end(info_hashes) + , [](announce_infohash const& ih) { return ih.is_working(); }); + } + + void announce_entry::trim() + { + while (!url.empty() && is_space(url[0])) + url.erase(url.begin()); + } +#endif +#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 + +TORRENT_VERSION_NAMESPACE_2_END + +namespace aux { + + announce_infohash::announce_infohash() + : fails(0) + , updating(false) + , start_sent(false) + , complete_sent(false) + , triggered_manually(false) + {} + + announce_endpoint::announce_endpoint(aux::listen_socket_handle const& s, bool const completed) + : local_endpoint(s ? s.get_local_endpoint() : tcp::endpoint()) + , enabled(true) + , socket(s) + { + TORRENT_UNUSED(completed); + } + + announce_entry::announce_entry(string_view u) + : url(u.to_string()) + , source(0) + , verified(false) + {} + + announce_entry::announce_entry(lt::announce_entry const& ae) + : url(ae.url) + , trackerid(ae.trackerid) + , tier(ae.tier) + , fail_limit(ae.fail_limit) + , source(ae.source) + , verified(false) + { + if (source == 0) source = lt::announce_entry::source_client; + } + + announce_entry::announce_entry() + : source(0) + , verified(false) + {} + + announce_entry::~announce_entry() = default; + announce_entry::announce_entry(announce_entry const&) = default; + announce_entry& announce_entry::operator=(announce_entry const&) & = default; + + void announce_infohash::reset() + { + start_sent = false; + next_announce = time_point32::min(); + min_announce = time_point32::min(); + } + + void announce_infohash::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_infohash::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_endpoint::reset() + { + for (auto& ih : info_hashes) + ih.reset(); + } + + void announce_entry::reset() + { + for (auto& aep : endpoints) + aep.reset(); + } + + announce_endpoint* announce_entry::find_endpoint(aux::listen_socket_handle const& s) + { + auto aep = std::find_if(endpoints.begin(), endpoints.end() + , [&](aux::announce_endpoint const& a) { return a.socket == s; }); + if (aep != endpoints.end()) return &*aep; + else return nullptr; + } +} // aux +} // libtorrent diff --git a/src/assert.cpp b/src/assert.cpp new file mode 100644 index 0000000..d623605 --- /dev/null +++ b/src/assert.cpp @@ -0,0 +1,407 @@ +/* + +Copyright (c) 2007-2020, Arvid Norberg +Copyright (c) 2008, Andrew Resch +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2020, Tiger Wang +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 && !defined TORRENT_WINRT + +#include "libtorrent/aux_/windows.hpp" +#include + +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 && !defined TORRENT_WINRT + +#include "libtorrent/aux_/windows.hpp" +#include "libtorrent/utf8.hpp" +#include + +#include +#include + +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 + // SIGABRT doesn't trigger a break with msvc + __debugbreak(); +#else + // send SIGABRT to the current process + // to break into the debugger + std::raise(SIGABRT); +#endif + std::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..03898a1 --- /dev/null +++ b/src/bandwidth_limit.cpp @@ -0,0 +1,110 @@ +/* + +Copyright (c) 2009, Georg Rudoy +Copyright (c) 2009-2011, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2018, 2020, 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/aux_/bandwidth_limit.hpp" +#include + +namespace libtorrent { +namespace aux { + + 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..c4f9b86 --- /dev/null +++ b/src/bandwidth_manager.cpp @@ -0,0 +1,225 @@ +/* + +Copyright (c) 2009, 2011, 2013-2014, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2015-2016, 2018, 2020, Alden Torres +Copyright (c) 2016, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_/bandwidth_manager.hpp" + +#if TORRENT_USE_ASSERTS +#include +#endif + +namespace libtorrent { +namespace aux { + + 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..625ebfc --- /dev/null +++ b/src/bandwidth_queue_entry.cpp @@ -0,0 +1,81 @@ +/* + +Copyright (c) 2009, Georg Rudoy +Copyright (c) 2009, 2012, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2018, 2020, 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 "libtorrent/aux_/bandwidth_queue_entry.hpp" + +namespace libtorrent { +namespace aux { + + 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..8fd8fa8 --- /dev/null +++ b/src/bdecode.cpp @@ -0,0 +1,1176 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2016-2017, Steven Siloti +Copyright (c) 2016-2017, Andrei Kurushin +Copyright (c) 2016-2017, 2019, Alden Torres +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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. +#include // for any_of + +#ifndef BOOST_SYSTEM_NOEXCEPT +#define BOOST_SYSTEM_NOEXCEPT throw() +#endif + +namespace libtorrent { + + using aux::bdecode_token; + +namespace { + + static_assert(int('0') == 48, "this code relies on ASCII compatible source encoding"); + static_assert(int('9') == 57, "this code relies on ASCII compatible source encoding"); + + 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 aux { + void escape_string(std::string& ret, char const* str, int len) + { + if (std::any_of(str, str + len, [](char const c) { return c < 32 || c >= 127; } )) + { + for (int i = 0; i < len; ++i) + { + char tmp[3]; + std::snprintf(tmp, sizeof(tmp), "%02x", std::uint8_t(str[i])); + ret += tmp; + } + } + else + { + ret.assign(str, std::size_t(len)); + } + } +} + + + + // 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'. + // WARNING: the val is an out-parameter that is expected to be initialized + // to 0 or a *positive* number. + char const* parse_int(char const* start, char const* end, char delimiter + , std::int64_t& val, bdecode_errors::error_code_enum& ec) + { + TORRENT_ASSERT(val >= 0); + while (start < end && *start != delimiter) + { + char const c = *start; + if (!numeric(c)) + { + ec = bdecode_errors::expected_digit; + return start; + } + if (val > std::numeric_limits::max() / 10) + { + ec = bdecode_errors::overflow; + return start; + } + val *= 10; + int const digit = c - '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) + { + 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)}; + } + + std::ptrdiff_t bdecode_node::data_offset() const noexcept + { + TORRENT_ASSERT_PRECOND(m_token_idx != -1); + if (m_token_idx == -1) return -1; + bdecode_token const& t = m_root_tokens[m_token_idx]; + return 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_node(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) + , bdecode_node(tokens, m_buffer, m_buffer_size, value_token)); + } + + std::pair bdecode_node::dict_at(int const i) const + { + bdecode_node key; + bdecode_node value; + std::tie(key, value) = dict_at_node(i); + return {key.string_value(), value}; + } + + 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]; + auto 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); + } + + std::ptrdiff_t bdecode_node::string_offset() const + { + TORRENT_ASSERT(type() == string_t); + bdecode_token const& t = m_root_tokens[m_token_idx]; + TORRENT_ASSERT(t.type == bdecode_token::string); + return t.offset + t.start_offset(); + } + + 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 > aux::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 > 32) + { + aux::escape_string(ret, str.data(), 25); + ret += "..."; + aux::escape_string(ret, str.data() + len - 4, 4); + } + else + { + aux::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..309344c --- /dev/null +++ b/src/bitfield.cpp @@ -0,0 +1,236 @@ +/* + +Copyright (c) 2016-2020, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Falcosc +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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) + { +#if defined __GNUC__ || defined __clang__ + ret += __builtin_popcountl(m_buf[i]); +#else + 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()); +#endif + } + + 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/bloom_filter.cpp b/src/bloom_filter.cpp new file mode 100644 index 0000000..cc45251 --- /dev/null +++ b/src/bloom_filter.cpp @@ -0,0 +1,77 @@ +/* + +Copyright (c) 2010-2011, 2016-2017, 2019, Arvid Norberg +Copyright (c) 2017, 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/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/bt_peer_connection.cpp b/src/bt_peer_connection.cpp new file mode 100644 index 0000000..99a2dfe --- /dev/null +++ b/src/bt_peer_connection.cpp @@ -0,0 +1,3803 @@ +/* + +Copyright (c) 2006-2020, Arvid Norberg +Copyright (c) 2007, Un Shyam +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016-2020, Alden Torres +Copyright (c) 2016-2018, Pavel Pimenov +Copyright (c) 2016-2017, Andrei Kurushin +Copyright (c) 2016-2020, Steven Siloti +Copyright (c) 2017, Antoine Dahan +Copyright (c) 2018, Greg Hazel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/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/peer_info.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/aux_/alloca.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/aux_/merkle.hpp" +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/aux_/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 (aux::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& 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 + + auto out_policy = static_cast(m_settings.get_int(settings_pack::out_enc_policy)); + +#ifdef TORRENT_SSL_PEERS + // 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(); + maybe_send_hash_request(); + } + + 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; + aux::write_uint16(listen_port, ptr); + send_buffer(msg); + + stats_counters().inc_stats_counter(counters::num_outgoing_dht_port); + } + + template + void bt_peer_connection::extension_notify(F message, Args... args) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + (*e.*message)(args...); + } +#endif + } + + 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); + +#ifndef TORRENT_DISABLE_EXTENSIONS + extension_notify(&peer_plugin::sent_have_all); +#endif + } + + 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); + +#ifndef TORRENT_DISABLE_EXTENSIONS + extension_notify(&peer_plugin::sent_have_none); +#endif + } + + 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); + +#ifndef TORRENT_DISABLE_EXTENSIONS + extension_notify(&peer_plugin::sent_reject_request, r); +#endif + } + + 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)); + +#ifndef TORRENT_DISABLE_EXTENSIONS + extension_notify(&peer_plugin::sent_allow_fast, piece); +#endif + } + + 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)); + +#ifndef TORRENT_DISABLE_EXTENSIONS + extension_notify(&peer_plugin::sent_suggest, piece); +#endif + } + + 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) + { + 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); + + hasher h; + sha1_hash const& info_hash = associated_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"); + } +#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 + aux::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; + + // we support FAST extension + *(ptr + 7) |= 0x04; + + // this is a v1 peer in a hybrid torrent + // indicate that we support upgrading to v2 + if (!peer_info_struct()->protocol_v2 && t->info_hash().has_v2()) + { + *(ptr + 7) |= 0x10; + } + +#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 = associated_info_hash(); + std::memcpy(ptr, ih.data(), ih.size()); + ptr += 20; + + std::memcpy(ptr, m_our_peer_id.data(), 20); + + TORRENT_ASSERT(!ih.is_all_zeros()); + +#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 {}; + + const char* ptr = recv_buffer.data() + 1; + peer_request r; + r.piece = piece_index_t(aux::read_int32(ptr)); + r.start = aux::read_int32(ptr); + r.length = m_recv_buffer.packet_size() - 9; + + // is any of the piece message header data invalid? + if (!validate_piece_request(r)) return {}; + + 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(aux::read_int32(ptr)); + + incoming_have(index); + maybe_send_hash_request(); + } + + // ----------------------------- + // --------- 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(aux::read_int32(ptr)); + r.start = aux::read_int32(ptr); + r.length = aux::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); + 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 const header_size = 9; + + peer_request p; + + if (recv_pos >= header_size) + { + const char* ptr = recv_buffer.data() + 1; + p.piece = piece_index_t(aux::read_int32(ptr)); + p.start = aux::read_int32(ptr); + 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) + { + // 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; + + incoming_piece(p, recv_buffer.data() + header_size); + maybe_send_hash_request(); + } + + // ----------------------------- + // ---------- 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(aux::read_int32(ptr)); + r.start = aux::read_int32(ptr); + r.length = aux::read_int32(ptr); + + incoming_cancel(r); + } + + void bt_peer_connection::on_hash_request(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); + + if (!peer_info_struct()->protocol_v2) + { + disconnect(errors::invalid_message, operation_t::bittorrent, peer_error); + return; + } + + if (m_recv_buffer.packet_size() != 1 + 32 + 4 + 4 + 4 + 4) + { + disconnect(errors::invalid_hash_request, operation_t::bittorrent, peer_connection_interface::peer_error); + return; + } + if (!m_recv_buffer.packet_finished()) return; + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + auto const& files = t->torrent_file().files(); + + span recv_buffer = m_recv_buffer.get(); + const char* ptr = recv_buffer.begin() + 1; + + auto const file_root = sha256_hash(ptr); + file_index_t const file_index = files.file_index_for_root(file_root); + ptr += sha256_hash::size(); + int const base = aux::read_int32(ptr); + int const index = aux::read_int32(ptr); + int const count = aux::read_int32(ptr); + int const proof_layers = aux::read_int32(ptr); + hash_request hr(file_index, base, index, count, proof_layers); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming_message)) + { + peer_log(peer_log_alert::incoming_message, "HASH_REQUEST" + , "file: %d base: %d idx: %d cnt: %d proofs: %d" + , static_cast(hr.file), hr.base, hr.index, hr.count, hr.proof_layers); + } +#endif + + if (!validate_hash_request(hr, files)) + { + write_hash_reject(hr, file_root); + return; + } + + std::vector hashes = t->get_hashes(hr); + + if (hashes.empty()) + { + write_hash_reject(hr, file_root); + return; + } + + write_hashes(hr, hashes); + } + + void bt_peer_connection::on_hashes(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); + + if (!peer_info_struct()->protocol_v2) + { + disconnect(errors::invalid_message, operation_t::bittorrent, peer_error); + return; + } + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + auto const& files = t->torrent_file().files(); + + span recv_buffer = m_recv_buffer.get(); + + int const header_size = 1 + 32 + 4 + 4 + 4 + 4; + + if (recv_buffer.size() < header_size) + { + return; + } + + const char* ptr = recv_buffer.begin() + 1; + + auto const file_root = sha256_hash(ptr); + file_index_t file_index{ -1 }; + for (file_index_t i : files.file_range()) + { + if (files.root(i) == file_root) + { + file_index = i; + break; + } + } + ptr += sha256_hash::size(); + int const base = aux::read_int32(ptr); + int const index = aux::read_int32(ptr); + int const count = aux::read_int32(ptr); + int const proof_layers = aux::read_int32(ptr); + + hash_request const hr(file_index, base, index, count, proof_layers); + + if (!validate_hash_request(hr, t->torrent_file().files())) + { + disconnect(errors::invalid_hashes, operation_t::bittorrent, peer_connection_interface::peer_error); + return; + } + + // subtract one because the the base layer doesn't count + int const proof_hashes = std::max(0 + , proof_layers - (merkle_num_layers(merkle_num_leafs(count)) - 1)); + + if (m_recv_buffer.packet_size() != header_size + + (count + proof_hashes) * int(sha256_hash::size())) + { + disconnect(errors::invalid_hashes, operation_t::bittorrent, peer_connection_interface::peer_error); + return; + } + + if (!m_recv_buffer.packet_finished()) return; + + auto new_end = std::remove(m_hash_requests.begin(), m_hash_requests.end(), hr); + m_hash_requests.erase(new_end, m_hash_requests.end()); + + std::vector hashes; + while (ptr != recv_buffer.end()) + { + hashes.emplace_back(ptr); + ptr += sha256_hash::size(); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming_message)) + { + peer_log(peer_log_alert::incoming_message, "HASHES" + , "file: %d base: %d idx: %d cnt: %d proofs: %d" + , static_cast(hr.file), hr.base, hr.index, hr.count, hr.proof_layers); + } +#endif + + if (!t->add_hashes(hr, hashes)) + { + disconnect(errors::invalid_hashes, operation_t::bittorrent, peer_connection_interface::peer_error); + return; + } + + maybe_send_hash_request(); + } + + void bt_peer_connection::on_hash_reject(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); + + if (!peer_info_struct()->protocol_v2) + { + disconnect(errors::invalid_message, operation_t::bittorrent, peer_error); + return; + } + + if (m_recv_buffer.packet_size() != 1 + 32 + 4 + 4 + 4 + 4) + { + disconnect(errors::invalid_hash_reject, operation_t::bittorrent, peer_connection_interface::peer_error); + return; + } + if (!m_recv_buffer.packet_finished()) return; + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + span recv_buffer = m_recv_buffer.get(); + const char* ptr = recv_buffer.begin() + 1; + + auto const file_root = sha256_hash(ptr); + file_index_t const file_index = t->torrent_file().files().file_index_for_root(file_root); + ptr += sha256_hash::size(); + int const base = aux::read_int32(ptr); + int const index = aux::read_int32(ptr); + int const count = aux::read_int32(ptr); + int const proof_layers = aux::read_int32(ptr); + hash_request hr(file_index, base, index, count, proof_layers); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming_message)) + { + peer_log(peer_log_alert::incoming_message, "HASH_REJECT" + , "file: %d base: %d idx: %d cnt: %d proofs: %d" + , static_cast(hr.file), hr.base, hr.index, hr.count, hr.proof_layers); + } +#endif + + auto new_end = std::remove(m_hash_requests.begin(), m_hash_requests.end(), hr); + if (new_end == m_hash_requests.end()) return; + m_hash_requests.erase(new_end, m_hash_requests.end()); + + t->hashes_rejected(hr); + + maybe_send_hash_request(); + } + + // ----------------------------- + // --------- 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 = aux::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(aux::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(); + maybe_send_hash_request(); + } + + 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(aux::read_int32(ptr)); + r.start = aux::read_int32(ptr); + r.length = aux::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(aux::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(aux::read_uint8(ptr)); + int const addr_type = aux::read_uint8(ptr); + + tcp::endpoint ep; + + if (addr_type == 0) + { + if (int(recv_buffer.size()) < 2 + 4 + 2) return; + // IPv4 address + ep = aux::read_v4_endpoint(ptr); + } + else if (addr_type == 1) + { + // IPv6 address + if (int(recv_buffer.size()) < 2 + 16 + 2) return; + ep = aux::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 = aux::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; + aux::write_uint8(type, ptr); + if (aux::is_v4(ep)) aux::write_uint8(0, ptr); + else aux::write_uint8(1, ptr); + aux::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) + { + aux::write_uint32(static_cast(error), ptr); + } + + // write the packet length and type + char* hdr = buf; + aux::write_uint32(ptr - buf - 4, hdr); + aux::write_uint8(msg_extended, hdr); + aux::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); + } + + void bt_peer_connection::write_hash_request(hash_request const& req) + { + INVARIANT_CHECK; + + char buf[5 + sha256_hash::size() + 4 * 4]; + char* ptr = buf; + aux::write_uint32(int(sizeof(buf) - 4), ptr); + aux::write_uint8(msg_hash_request, ptr); + + auto t = associated_torrent().lock(); + if (!t) return; + auto const& ti = t->torrent_file(); + auto const& fs = ti.files(); + auto const root = fs.root(req.file); + + ptr = std::copy(root.begin(), root.end(), ptr); + + TORRENT_ASSERT(validate_hash_request(req, t->torrent_file().files())); + + aux::write_uint32(req.base, ptr); + aux::write_uint32(req.index, ptr); + aux::write_uint32(req.count, ptr); + aux::write_uint32(req.proof_layers, ptr); + + stats_counters().inc_stats_counter(counters::num_outgoing_hash_request); + + m_hash_requests.push_back(req); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing_message)) + { + peer_log(peer_log_alert::outgoing_message, "HASH_REQUEST" + , "file: %d base: %d idx: %d cnt: %d proofs: %d" + , int(req.file), req.base, req.index, req.count, req.proof_layers); + } +#endif + + send_buffer(buf); + } + + void bt_peer_connection::write_hashes(hash_request const& req, span hashes) + { + INVARIANT_CHECK; + + int const packet_size = int(5 + sha256_hash::size() + + 4 * 4 + + sha256_hash::size() * hashes.size()); + TORRENT_ALLOCA(buf, char, packet_size); + char* ptr = buf.data(); + aux::write_uint32(packet_size - 4, ptr); + aux::write_uint8(msg_hashes, ptr); + + auto t = associated_torrent().lock(); + if (!t) return; + auto const& ti = t->torrent_file(); + auto const& fs = ti.files(); + auto root = fs.root(req.file); + + ptr = std::copy(root.begin(), root.end(), ptr); + + aux::write_uint32(req.base, ptr); + aux::write_uint32(req.index, ptr); + aux::write_uint32(req.count, ptr); + aux::write_uint32(req.proof_layers, ptr); + + for (auto const& h : hashes) + ptr = std::copy(h.begin(), h.end(), ptr); + + stats_counters().inc_stats_counter(counters::num_outgoing_hashes); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing_message)) + { + peer_log(peer_log_alert::outgoing_message, "HASHES" + , "file: %d base: %d idx: %d cnt: %d proofs: %d" + , static_cast(req.file), req.base, req.index, req.count, req.proof_layers); + } +#endif + + send_buffer(buf); + } + + void bt_peer_connection::write_hash_reject(hash_request const& req, sha256_hash const& root) + { + INVARIANT_CHECK; + + char buf[5 + sha256_hash::size() + 4 * 4]; + char* ptr = buf; + aux::write_uint32(int(sizeof(buf) - 4), ptr); + aux::write_uint8(msg_hash_reject, ptr); + + auto t = associated_torrent().lock(); + if (!t) return; + ptr = std::copy(root.begin(), root.end(), ptr); + + aux::write_uint32(req.base, ptr); + aux::write_uint32(req.index, ptr); + aux::write_uint32(req.count, ptr); + aux::write_uint32(req.proof_layers, ptr); + + stats_counters().inc_stats_counter(counters::num_outgoing_hash_reject); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing_message)) + { + peer_log(peer_log_alert::outgoing_message, "HASH_REJECT" + , "base: %d idx: %d cnt: %d proofs: %d" + , req.base, req.index, req.count, req.proof_layers); + } +#endif + + send_buffer(buf); + } + + void bt_peer_connection::maybe_send_hash_request() + { + if (is_disconnecting() || m_hash_requests.size() > 1) return; + if (!peer_info_struct()->protocol_v2) return; + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + if (!t->valid_metadata()) return; + + auto req = t->pick_hashes(this); + if (req.count > 0) write_hash_request(req); + } + + // ----------------------------- + // --------- 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 + aux::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() + , make_address_v4(v4_mapped, ipv6_address) + , 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 + && can_disconnect(errors::upload_upload_connection)) + 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 const packet_type = static_cast(recv_buffer[0]); + +#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; + case msg_hash_request: on_hash_request(received); break; + case msg_hashes: on_hashes(received); break; + case msg_hash_reject: on_hash_reject(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, peer_error); + 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; + aux::write_uint8(m_upload_only_id, ptr); + aux::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; + aux::write_uint8(m_share_mode_id, ptr); + aux::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); + +#ifndef TORRENT_DISABLE_EXTENSIONS + extension_notify(&peer_plugin::sent_cancel, r); +#endif + } + + 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); + +#ifndef TORRENT_DISABLE_EXTENSIONS + extension_notify(&peer_plugin::sent_request, r); +#endif + } + + 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(); + + aux::write_int32(packet_size - 4, ptr); + aux::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; + auto 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); + aux::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 + aux::write_int32(int(dict_msg.size()) + 2, ptr); + aux::write_uint8(msg_extended, ptr); + // signal handshake message + aux::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); + +#ifndef TORRENT_DISABLE_EXTENSIONS + extension_notify(&peer_plugin::sent_choke); +#endif + } + + void bt_peer_connection::write_unchoke() + { + INVARIANT_CHECK; + + send_message(msg_unchoke, counters::num_outgoing_unchoke); + +#ifndef TORRENT_DISABLE_EXTENSIONS + extension_notify(&peer_plugin::sent_unchoke); +#endif + } + + void bt_peer_connection::write_interested() + { + INVARIANT_CHECK; + + send_message(msg_interested, counters::num_outgoing_interested); + +#ifndef TORRENT_DISABLE_EXTENSIONS + extension_notify(&peer_plugin::sent_interested); +#endif + } + + void bt_peer_connection::write_not_interested() + { + INVARIANT_CHECK; + + send_message(msg_not_interested, counters::num_outgoing_not_interested); + +#ifndef TORRENT_DISABLE_EXTENSIONS + extension_notify(&peer_plugin::sent_not_interested); +#endif + } + + 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)); + +#ifndef TORRENT_DISABLE_EXTENSIONS + extension_notify(&peer_plugin::sent_have, index); +#endif + } + + 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; + aux::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); + TORRENT_ASSERT(r.length >= 0); + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + // 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); + aux::write_int32(r.length + 1 + 4 + 4, ptr); + aux::write_uint8(msg_piece, ptr); + aux::write_int32(static_cast(r.piece), ptr); + aux::write_int32(r.start, ptr); + + 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); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + extension_notify(&peer_plugin::sent_piece, r); +#endif + } + + // -------------------------- + // 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); + } + +#if !defined TORRENT_DISABLE_ENCRYPTION + void bt_peer_connection::init_bt_handshake() + { + m_encrypted = true; + if (m_rc4_encrypted) + { + switch_send_crypto(m_rc4); + switch_recv_crypto(m_rc4); + } + + // 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(); + + // 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 + + 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); + } + + // compute the obfuscated hash of the torrent's valid info hashes + // to find the one which matches the received hash + + sha1_hash oih(ih); + oih ^= m_dh_key_exchange->get_hash_xor_mask(); + + t->info_hash().for_each([&](sha1_hash const& tih, protocol_version v) + { + static char const req2[4] = { 'r', 'e', 'q', '2' }; + hasher h(req2); + h.update(tih); + if (h.final() == oih) + peer_info_struct()->protocol_v2 = v == protocol_version::V2; + }); + + m_rc4 = init_pe_rc4_handler(m_dh_key_exchange->get_secret() + , associated_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 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; + } + + // len(IA) at the end of pad + if (!is_outgoing()) + len_pad += 2; + + if (len_pad > 0) + { + m_state = state_t::read_pe_pad; + m_recv_buffer.reset(len_pad); + } + else + { + TORRENT_ASSERT(len_pad == 0); + init_bt_handshake(); + m_state = state_t::read_protocol_identifier; + m_recv_buffer.reset(20); + } + } + + 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); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", "len(IA) : %d", len_ia); +#endif + if (len_ia < 0 || len_ia > 68) + { + disconnect(errors::invalid_encrypt_handshake, operation_t::encryption, peer_error); + return; + } + + if (len_ia == 0) + { + // everything after this is encrypted + init_bt_handshake(); + m_state = state_t::read_protocol_identifier; + m_recv_buffer.reset(20); + } + else + { + // The other peer indicated that a non-zero bytes will be + // encrypted at the start of the underlying bittorrent + // protocol. This number of bytes, len_ia, is not + // necessarily aligned to message boundaries. We first read + // that many bytes, decrypt it, and then pass it back into + // the regular protocol parser + m_state = state_t::read_pe_ia; + m_recv_buffer.reset(len_ia); + } + } + else // is_outgoing() + { + // everything that arrives after this is encrypted + init_bt_handshake(); + m_state = state_t::read_protocol_identifier; + m_recv_buffer.reset(20); + } + } + + if (m_state == state_t::read_pe_ia) + { + TORRENT_ASSERT(!is_outgoing()); + TORRENT_ASSERT(!m_encrypted); + + if (!m_recv_buffer.packet_finished()) return; + + // the IA bytes are 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(); + + // now that we have decrypted IA length of bytes, we + // reinterpret the receive buffer as the very start of a normal + // connection. First we expect to find the protocol identifier + // (i.e. "BitTorrent Protocol") + m_state = state_t::read_protocol_identifier; + m_recv_buffer.cut(0, 20); + } + +#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_SSL_PEERS + 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_SSL_PEERS + + 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; + // we're "cutting" off 0 bytes from the receive buffer here + // because we want to interpret it as something else. It didn't + // contain the expected bittorrent handshake string, so let's + // try again to interpret it as an encrypted handshake + 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%s" + , extensions.c_str() + , (recv_buffer[7] & 0x01) ? "DHT " : "" + , (recv_buffer[7] & 0x04) ? "FAST " : "" + , (recv_buffer[7] & 0x10) ? "v2 " : "" + , (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_t(info_hash)); + if (is_disconnecting()) return; + + t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + // this must go after the connection is attached to a torrent because that is what + // adds the peer info for incoming connections + if (recv_buffer[7] & 0x10) + { + if (t->valid_metadata() && !t->info_hash().has_v2()) + { + // the peer claims to support the v2 protocol with a non-v2 torrent + disconnect(errors::invalid_info_hash, operation_t::bittorrent); + return; + } + peer_info_struct()->protocol_v2 = true; + } + } + else + { + // verify info hash + // also check for all zero info hash in the torrent to make sure + // the client isn't attempting to use a protocol version the torrent + // doesn't support + if (std::equal(recv_buffer.begin() + 8, recv_buffer.begin() + 28 + , t->info_hash().get(protocol_version::V2).data()) + && t->info_hash().has_v2()) + { + peer_info_struct()->protocol_v2 = true; + } + else if (!std::equal(recv_buffer.begin() + 8, recv_buffer.begin() + 28 + , associated_info_hash().data()) + || associated_info_hash().is_all_zeros()) + { +#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 + } + + // 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(); + maybe_send_hash_request(); + + // 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 = aux::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..aba8dbc --- /dev/null +++ b/src/chained_buffer.cpp @@ -0,0 +1,174 @@ +/* + +Copyright (c) 2011, 2014, 2016-2019, Arvid Norberg +Copyright (c) 2016-2018, 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/aux_/chained_buffer.hpp" +#include "libtorrent/assert.hpp" + +#include // for copy + +namespace libtorrent { +namespace aux { + + 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..905a3c2 --- /dev/null +++ b/src/choker.cpp @@ -0,0 +1,311 @@ +/* + +Copyright (c) 2019, Amir Abrams +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2019, Monson Shao +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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; + } + + } // anonymous namespace + + int unchoke_sort(std::vector& peers + , 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 + + 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() + , [](peer_connection const* lhs, peer_connection const* rhs) + { return upload_rate_compare(lhs, rhs); }); + + 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() + , [pieces](peer_connection const* lhs, peer_connection const* rhs) + { return unchoke_compare_rr(lhs, rhs, 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() + , [](peer_connection const* lhs, peer_connection const* rhs) + { return unchoke_compare_fastest_upload(lhs, rhs); }); + } + else if (sett.get_int(settings_pack::seed_choking_algorithm) + == settings_pack::anti_leech) + { + std::nth_element(peers.begin(), peers.begin() + + slots, peers.end() + , [](peer_connection const* lhs, peer_connection const* rhs) + { return unchoke_compare_anti_leech(lhs, rhs); }); + } + else + { + int const pieces = sett.get_int(settings_pack::seeding_piece_quota); + std::nth_element(peers.begin(), peers.begin() + + slots, peers.end() + , [pieces](peer_connection const* lhs, peer_connection const* rhs) + { return unchoke_compare_rr(lhs, rhs, 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..da9f7cd --- /dev/null +++ b/src/close_reason.cpp @@ -0,0 +1,167 @@ +/* + +Copyright (c) 2015-2017, 2019, Arvid Norberg +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/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/copy_file.cpp b/src/copy_file.cpp new file mode 100644 index 0000000..50b652b --- /dev/null +++ b/src/copy_file.cpp @@ -0,0 +1,431 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_/path.hpp" + +#ifdef TORRENT_WINDOWS +// windows part +#include "libtorrent/aux_/windows.hpp" +#else + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#include +#include + +#if TORRENT_HAS_COPYFILE +#include +#endif + +#endif + +namespace libtorrent { + +#ifdef TORRENT_WINDOWS +namespace { + +// returns true if the given file has any regions that are +// sparse, i.e. not allocated. This is similar to calling lseek(SEEK_DATA) and +// lseek(SEEK_HOLE) +std::pair next_allocated_region(HANDLE file + , std::int64_t const offset + , std::int64_t file_size + , error_code& ec) +{ +#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 = offset; + in.Length.QuadPart = file_size - offset; + + FILE_ALLOCATED_RANGE_BUFFER out; + + DWORD returned_bytes = 0; + BOOL const ret = DeviceIoControl(file, FSCTL_QUERY_ALLOCATED_RANGES + , static_cast(&in), sizeof(in) + , &out, sizeof(out), &returned_bytes, nullptr); + + if (ret == FALSE) + { + int const error = ::GetLastError(); + // we expect this error, since we just ask for one allocated range at a + // time. + if (error != ERROR_MORE_DATA) + { + ec.assign(error, system_category()); + return {0, 0}; + } + } + + if (returned_bytes != sizeof(out)) { + return {file_size, file_size}; + } + + return {out.FileOffset.QuadPart, out.FileOffset.QuadPart + out.Length.QuadPart}; +} + +struct file_handle +{ + file_handle(HANDLE h) : m_h(h) {} + + ~file_handle() + { + if (m_h != INVALID_HANDLE_VALUE) ::CloseHandle(m_h); + } + + file_handle(file_handle const&) = delete; + file_handle(file_handle&& rhs) + : m_h(rhs.m_h) + { + rhs.m_h = INVALID_HANDLE_VALUE; + } + HANDLE handle() const { return m_h; } +private: + HANDLE m_h; +}; + +void copy_range(HANDLE const in_handle, HANDLE const out_handle + , std::int64_t in_offset, std::int64_t len, error_code& ec) +{ + char buffer[16384]; + while (len > 0) + { + OVERLAPPED in_ol{}; + in_ol.Offset = in_offset & 0xffffffff; + in_ol.OffsetHigh = in_offset >> 32; + DWORD num_read = 0; + if (ReadFile(in_handle, buffer, DWORD(std::min(len, std::int64_t(sizeof(buffer)))) + , &num_read, &in_ol) == 0) + { + int const error = ::GetLastError(); + if (error == ERROR_HANDLE_EOF) return; + + ec.assign(error, system_category()); + return; + } + + len -= num_read; + int buf_offset = 0; + while (num_read > 0) + { + OVERLAPPED out_ol{}; + out_ol.Offset = in_offset & 0xffffffff; + out_ol.OffsetHigh = in_offset >> 32; + DWORD num_written = 0; + if (WriteFile(out_handle, buffer + buf_offset, DWORD(num_read - buf_offset) + , &num_written, &out_ol) == 0) + { + ec.assign(::GetLastError(), system_category()); + return; + } + buf_offset += num_written; + num_read -= num_written; + in_offset += num_written; + } + } + return; +} + +} + +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); + + WIN32_FILE_ATTRIBUTE_DATA in_stat; + if (!GetFileAttributesExW(f1.c_str(), GetFileExInfoStandard, &in_stat)) + { + ec.assign(GetLastError(), system_category()); + return; + } + + if ((in_stat.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) == 0) + { + // if the input file is not sparse, use the system copy function + if (CopyFileW(f1.c_str(), f2.c_str(), false) == 0) + ec.assign(GetLastError(), system_category()); + return; + } + + std::int64_t const in_size = (std::int64_t(in_stat.nFileSizeHigh) << 32) + | in_stat.nFileSizeLow; + +#ifdef TORRENT_WINRT + file_handle in_handle = ::CreateFile2(f1.c_str() + , GENERIC_READ + , FILE_SHARE_READ + , OPEN_EXISTING + , nullptr); +#else + file_handle in_handle = ::CreateFileW(f1.c_str() + , GENERIC_READ + , FILE_SHARE_READ + , nullptr + , OPEN_EXISTING + , FILE_FLAG_SEQUENTIAL_SCAN + , nullptr); +#endif + if (in_handle.handle() == INVALID_HANDLE_VALUE) + { + ec.assign(GetLastError(), system_category()); + return; + } + +#ifdef TORRENT_WINRT + file_handle out_handle = ::CreateFile2(f1.c_str() + , GENERIC_WRITE + , FILE_SHARE_WRITE + , OPEN_ALWAYS + , nullptr); +#else + file_handle out_handle = ::CreateFileW(f2.c_str() + , GENERIC_WRITE + , FILE_SHARE_WRITE + , nullptr + , OPEN_ALWAYS + , FILE_FLAG_WRITE_THROUGH + , nullptr); +#endif + if (out_handle.handle() == INVALID_HANDLE_VALUE) + { + ec.assign(GetLastError(), system_category()); + return; + } + + DWORD temp; + if (::DeviceIoControl(out_handle.handle(), FSCTL_SET_SPARSE + , nullptr, 0, nullptr, 0, &temp, nullptr) == 0) + { + ec.assign(GetLastError(), system_category()); + return; + } + + std::pair data(0, 0); + for (;;) + { + data = next_allocated_region(in_handle.handle(), data.second, in_size, ec); + if (ec) return; + + copy_range(in_handle.handle(), out_handle.handle(), data.first, data.second - data.first, ec); + if (ec) return; + // There's a possible time-of-check-time-of-use race here. + // The source file may have grown during the copy operation, in which + // case data.second may exceed the initial size + if (data.second >= in_size) return; + } +} + +#else +// Generic/linux implementation + +namespace { + +struct file_descriptor +{ + file_descriptor(int fd) : m_fd(fd) {} + + ~file_descriptor() + { + if (m_fd >= 0) ::close(m_fd); + } + + file_descriptor(file_descriptor const&) = delete; + file_descriptor(file_descriptor&& rhs) + : m_fd(rhs.m_fd) + { + rhs.m_fd = -1; + } + int fd() const { return m_fd; } +private: + int m_fd; +}; + +ssize_t copy_range(int const fd_in, int const fd_out, off_t in_offset + , std::int64_t len, error_code& ec) +{ +#if TORRENT_HAS_COPY_FILE_RANGE + off_t out_offset = in_offset; + ssize_t ret = 0; + do + { + ret = ::copy_file_range(fd_in, &in_offset + , fd_out, &out_offset, std::size_t(len), 0); + if (ret < 0) + { + ec.assign(errno, system_category()); + return -1; + } + + len -= ret; + } while (len > 0 && ret > 0); + return ret; +#else + char buffer[16384]; + ssize_t total_copied = 0; + while (len > 0) + { + ssize_t num_read = ::pread(fd_in, buffer + , std::size_t(std::min(len, std::int64_t(sizeof(buffer)))), in_offset); + if (num_read == 0) return total_copied; + if (num_read < 0) + { + ec.assign(errno, system_category()); + return -1; + } + len -= num_read; + int buf_offset = 0; + while (num_read > 0) + { + auto const ret = ::pwrite(fd_out, buffer + buf_offset + , std::size_t(num_read - buf_offset), in_offset); + if (ret <= 0) + { + ec.assign(errno, system_category()); + return -1; + } + buf_offset += ret; + num_read -= ret; + in_offset += ret; + total_copied += ret; + } + } + return total_copied; +#endif +} + +} // anonymous namespace + +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); + + file_descriptor const infd = ::open(f1.c_str(), O_RDONLY); + if (infd.fd() < 0) + { + ec.assign(errno, system_category()); + return; + } + + struct stat in_stat; + if (::fstat(infd.fd(), &in_stat) != 0) + { + ec.assign(errno, system_category()); + return; + } + + bool const input_is_sparse = in_stat.st_size > off_t(in_stat.st_blocks) * 512; + + // if the source file is not sparse we'll end up copying every byte anyway, + // there's no point in passing O_TRUNC. However, in order to preserve sparse + // regions, we *do* need to truncate the output file. + file_descriptor const outfd = ::open(f2.c_str() + , input_is_sparse ? (O_RDWR | O_CREAT | O_TRUNC) : (O_RDWR | O_CREAT), in_stat.st_mode); + if (outfd.fd() < 0) + { + ec.assign(errno, system_category()); + return; + } + +#if TORRENT_HAS_COPYFILE + if (!input_is_sparse) + { + // the the file isn't sparse use the system copy function (which + // expands sparse regions) + // this only works on 10.5 + copyfile_state_t state = copyfile_state_alloc(); + if (fcopyfile(infd.fd(), outfd.fd(), state, COPYFILE_ALL) < 0) + ec.assign(errno, system_category()); + copyfile_state_free(state); + return; + } +#endif + + if (::ftruncate(outfd.fd(), in_stat.st_size) < 0) + { + ec.assign(errno, system_category()); + return; + } + +#ifdef SEEK_HOLE + if (input_is_sparse) + { + ssize_t ret = 0; + off_t data_start = 0; + off_t data_end = 0; + for (;;) + { + data_start = ::lseek(infd.fd(), data_end, SEEK_DATA); + if (data_start == off_t(-1)) + { + ec.assign(errno, system_category()); + return; + } + + data_end = ::lseek(infd.fd(), data_start, SEEK_HOLE); + if (data_end == off_t(-1)) + { + ec.assign(errno, system_category()); + return; + } + + ret = copy_range(infd.fd(), outfd.fd(), data_start, data_end - data_start, ec); + if (ret <= 0) return; + if (data_end == in_stat.st_size) return; + } + } +#endif + + copy_range(infd.fd(), outfd.fd(), 0, in_stat.st_size, ec); +} + +#endif // TORRENT_WINDOWS + +} + diff --git a/src/cpuid.cpp b/src/cpuid.cpp new file mode 100644 index 0000000..72e59b8 --- /dev/null +++ b/src/cpuid.cpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2018, Alexandre Janniaux +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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) noexcept + { +#if defined _MSC_VER + __cpuid(reinterpret_cast(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() noexcept + { +#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() noexcept + { +#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() noexcept + { +#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() noexcept + { +#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..f1a415d --- /dev/null +++ b/src/crc32c.cpp @@ -0,0 +1,151 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2018, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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..54c16b5 --- /dev/null +++ b/src/create_torrent.cpp @@ -0,0 +1,998 @@ +/* + +Copyright (c) 2008-2020, Arvid Norberg +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2016-2017, 2019-2020, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/mmap_disk_io.hpp" // for hasher_thread_divisor +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/aux_/merkle.hpp" // for merkle_*() +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/session.hpp" // for default_disk_io_constructor +#include "libtorrent/aux_/directory.hpp" +#include "libtorrent/disk_interface.hpp" + +#include +#include + +#include +#include + +using namespace std::placeholders; + +namespace libtorrent { + +#if TORRENT_ABI_VERSION <= 2 + constexpr create_flags_t create_torrent::optimize_alignment; +#endif +#if TORRENT_ABI_VERSION == 1 + constexpr create_flags_t create_torrent::optimize; +#endif +#if TORRENT_ABI_VERSION <= 2 + constexpr create_flags_t create_torrent::merkle; +#endif + constexpr create_flags_t create_torrent::modification_time; + constexpr create_flags_t create_torrent::symlinks; +#if TORRENT_ABI_VERSION <= 2 + constexpr create_flags_t create_torrent::mutable_torrent_support; +#endif + constexpr create_flags_t create_torrent::v2_only; + constexpr create_flags_t create_torrent::v1_only; + constexpr create_flags_t create_torrent::canonical_files; + constexpr create_flags_t create_torrent::no_attributes; + +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 (aux::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 = (flags & create_torrent::no_attributes) + ? file_flags_t{} + : 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_interface& iothread; + piece_index_t piece_counter; + piece_index_t completed_piece; + std::function const& f; + error_code& ec; + }; + + void on_hash(aux::vector v2_blocks, 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; + } + + if (!st->ct.is_v2_only()) + st->ct.set_hash(piece, piece_hash); + + if (!st->ct.is_v1_only()) + { + file_index_t const current_file = st->ct.files().file_index_at_piece(piece); + if (!st->ct.files().pad_file_at(current_file)) + { + piece_index_t const file_first_piece(int(st->ct.files().file_offset(current_file) / st->ct.piece_length())); + TORRENT_ASSERT(st->ct.files().file_offset(current_file) % st->ct.piece_length() == 0); + + auto const file_piece_offset = piece - file_first_piece; + auto const file_size = st->ct.files().file_size(current_file); + TORRENT_ASSERT(file_size > 0); + auto const file_blocks = st->ct.files().file_num_blocks(current_file); + auto const piece_blocks = st->ct.files().blocks_in_piece2(piece); + int const num_leafs = merkle_num_leafs(file_blocks); + // If the file is smaller than one piece then the block hashes + // should be padded to the next power of two instead of the next + // piece boundary. + int const padded_leafs = file_size < st->ct.piece_length() + ? num_leafs + : st->ct.piece_length() / default_block_size; + + TORRENT_ASSERT(padded_leafs <= int(v2_blocks.size())); + for (auto i = piece_blocks; i < padded_leafs; ++i) + v2_blocks[i].clear(); + sha256_hash const piece_root = merkle_root( + span(v2_blocks).first(padded_leafs)); + st->ct.set_hash2(current_file, file_piece_offset, piece_root); + } + } + + auto flags = disk_interface::sequential_access; + if (!st->ct.is_v2_only()) flags |= disk_interface::v1_hash; + + st->f(st->completed_piece); + ++st->completed_piece; + if (st->piece_counter < st->ct.files().end_piece()) + { + span v2_span(v2_blocks); + st->iothread.async_hash(st->storage, st->piece_counter, v2_span, flags + , std::bind(&on_hash, std::move(v2_blocks), _1, _2, _3, st)); + ++st->piece_counter; + st->iothread.submit_jobs(); + } + else if (st->completed_piece == st->ct.files().end_piece()) + { + st->iothread.abort(true); + } + } + +} // 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 + + 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_interface& 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_interface& m_dio; + }; +} + + void set_piece_hashes(create_torrent& t, std::string const& p + , std::function const& f, error_code& ec) + { + aux::session_settings sett; + int const num_threads = std::max(1, static_cast(std::thread::hardware_concurrency() / 2)); + sett.set_int(settings_pack::hashing_threads, num_threads); + set_piece_hashes(t, p, sett, f, ec); + } + + void set_piece_hashes(create_torrent& t, std::string const& p + , settings_interface const& sett + , std::function const& f, error_code& ec) + { + set_piece_hashes(t, p, sett, default_disk_io_constructor, f, ec); + } + + void set_piece_hashes(create_torrent& t, std::string const& p + , settings_interface const& sett, disk_io_constructor_type disk_io + , std::function const& f, error_code& ec) + { + // optimized path +#ifdef TORRENT_BUILD_SIMULATOR + sim::default_config conf; + sim::simulation sim{conf}; + io_context ios{sim}; +#else + io_context 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; + int const num_threads = sett.get_int(settings_pack::hashing_threads); + std::unique_ptr disk_thread = disk_io(ios, sett, cnt); + disk_aborter da(*disk_thread.get()); + + 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(params + , std::shared_ptr()); + + // have 4 outstanding hash requests per thread, and no less than 1 MiB + int const jobs_per_thread = 4; + int const piece_read_ahead = std::max(num_threads * jobs_per_thread + , 1 * 1024 * 1024 / t.piece_length()); + + hash_state st = { t, std::move(storage), *disk_thread.get(), piece_index_t(0), piece_index_t(0), f, ec }; + for (piece_index_t i(0); i < piece_index_t(piece_read_ahead); ++i) + { + aux::vector v2_blocks; + + if (!t.is_v1_only()) + v2_blocks.resize(t.piece_length() / default_block_size); + + auto flags = disk_interface::sequential_access; + if (!t.is_v2_only()) flags |= disk_interface::v1_hash; + + // the span needs to be created before the call to async_hash to ensure that + // it is constructed before the vector is moved into the bind context + span v2_span(v2_blocks); + disk_thread->async_hash(st.storage, i, v2_span, flags + , std::bind(&on_hash, std::move(v2_blocks), _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(); +#endif + if (st.ec) { + ec = st.ec; + } + } + + create_torrent::~create_torrent() = default; + + create_torrent::create_torrent(file_storage& fs, int piece_size + , create_flags_t const flags) + : m_files(fs) + , m_creation_date(::time(nullptr)) + , m_multifile(fs.num_files() > 1) + , m_private(false) + , m_include_mtime(bool(flags & create_torrent::modification_time)) + , m_include_symlinks(bool(flags & create_torrent::symlinks)) + , m_v2_only(bool(flags & create_torrent::v2_only)) + , m_v1_only(bool(flags & create_torrent::v1_only)) + { + // 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) + { + // 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; + } + + if (!(flags & v1_only)) + { + // v2 torrents requires piece sizes to be at least 16 kiB + piece_size = std::max(piece_size, 16 * 1024); + + // make sure the size is an even power of 2 + // i.e. only a single bit is set. This is required by v2 torrents + if ((piece_size & (piece_size - 1)) != 0) + aux::throw_ex(errors::invalid_piece_size); + } + else if ((piece_size % (16 * 1024)) != 0 + && (piece_size & (piece_size - 1)) != 0) + { + // v1 torrents should have piece sizes divisible by 16 kiB + aux::throw_ex(errors::invalid_piece_size); + } + + fs.set_piece_length(piece_size); + if (!(flags & v1_only) || (flags & canonical_files)) + fs.canonicalize(); + fs.set_num_pieces(aux::calc_num_pieces(fs)); + TORRENT_ASSERT(fs.piece_length() > 0); + } + + create_torrent::create_torrent(torrent_info const& ti) + : m_files(ti.files()) + , m_creation_date(::time(nullptr)) + , m_multifile(ti.num_files() > 1) + , m_private(ti.priv()) + , m_include_mtime(false) + , m_include_symlinks(false) + , m_v2_only(!ti.info_hashes().has_v1()) + , m_v1_only(!ti.info_hashes().has_v2()) + { + bool const make_v1 = ti.info_hashes().has_v1(); + bool const make_v2 = ti.info_hashes().has_v2(); + + TORRENT_ASSERT_PRECOND(make_v2 || make_v1); + TORRENT_ASSERT_PRECOND(ti.is_valid()); + TORRENT_ASSERT_PRECOND(ti.num_pieces() > 0); + TORRENT_ASSERT_PRECOND(ti.num_files() > 0); + TORRENT_ASSERT_PRECOND(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); + } + + if (make_v1) + { + m_piece_hash.resize(m_files.num_pieces()); + for (auto const i : m_files.piece_range()) + set_hash(i, ti.hash_for_piece(i)); + } + + if (make_v2) + { + m_fileroots.resize(m_files.num_files()); + m_file_piece_hash.resize(m_files.num_files()); + for (auto const i : m_files.file_range()) + { + // don't include merkle hash trees for pad files + if (m_files.pad_file_at(i)) continue; + if (m_files.file_size(i) == 0) continue; + + auto const file_size = m_files.file_size(i); + if (file_size <= m_files.piece_length()) + { + set_hash2(i, piece_index_t::diff_type{0}, m_files.root(i)); + continue; + } + + span pieces = ti.piece_layer(i); + + piece_index_t::diff_type p{0}; + for (int h = 0; h < int(pieces.size()); h += int(sha256_hash::size())) + set_hash2(i, p++, sha256_hash(pieces.data() + h)); + } + } + + auto const info = ti.info_section(); + m_info_dict.preformatted().assign(info.data(), info.data() + info.size()); + } + +namespace { + bool validate_v2_hashes(file_storage const& fs + , aux::vector, file_index_t> const& file_piece_hash) + { + if (int(file_piece_hash.size()) != fs.num_files()) return false; + + int const piece_size = fs.piece_length(); + + for (auto i : fs.file_range()) + { + auto const& hashes = file_piece_hash[i]; + + // pad files are not supposed to have any hashes + if (fs.pad_file_at(i)) + { + if (!hashes.empty()) return false; + continue; + } + + if (int(hashes.size()) != (fs.file_size(i) + piece_size - 1) / piece_size) return false; + if (std::any_of(hashes.begin(), hashes.end(), [](sha256_hash const& h) + { return h.is_all_zeros(); })) + { + return false; + } + } + return true; + } + + bool validate_v1_hashes(file_storage const& fs + , aux::vector piece_hash) + { + int const piece_size = fs.piece_length(); + if (int(piece_hash.size()) != (fs.total_size() + piece_size - 1) / piece_size) + return false; + + return !std::any_of(piece_hash.begin(), piece_hash.end() + , [](sha1_hash const& h) { return h.is_all_zeros(); }); + } + + void add_file_attrs(entry& e, file_flags_t const flags, bool const include_symlinks) + { + if (!(flags & (file_storage::flag_pad_file + | file_storage::flag_hidden + | file_storage::flag_executable + | file_storage::flag_symlink))) + { + return; + } + std::string& attr = 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 (include_symlinks && (flags & file_storage::flag_symlink)) attr += 'l'; + } + + void add_symlink_path(entry& e, std::string symlink_path) + { + entry& sympath_e = e["symlink path"]; + + std::string const link = lexically_relative("", symlink_path); + for (auto elems = lsplit_path(link); !elems.first.empty(); + elems = lsplit_path(elems.second)) + sympath_e.list().emplace_back(elems.first); + } +} + + entry create_torrent::generate() const + { + if (m_files.num_files() == 0 || m_files.total_size() == 0) + aux::throw_ex(errors::torrent_missing_file_tree); + + // if all v2 hashes are set correctly, generate the v2 parts of the + // torrent + bool const make_v2 = validate_v2_hashes(m_files, m_file_piece_hash); + bool const make_v1 = validate_v1_hashes(m_files, m_piece_hash); + + // if neither v1 nor v2 hashes were set, we can't create a torrent + if (!make_v1 && !make_v2) + aux::throw_ex(errors::invalid_hash_entry); + + TORRENT_ASSERT(m_files.piece_length() > 0); + + entry dict; + + 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; + + if (m_creation_date != 0) + 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); + } + } + } + + if (make_v2) + { + TORRENT_ASSERT(!m_file_piece_hash.empty()); + m_fileroots.resize(m_files.num_files()); + + sha256_hash const pad_hash = merkle_pad(m_files.piece_length() / default_block_size, 1); + auto& file_pieces = dict["piece layers"].dict(); + + for (file_index_t fi : m_files.file_range()) + { + if (files().file_flags(fi) & file_storage::flag_pad_file) continue; + if (files().file_size(fi) == 0) continue; + + m_fileroots[fi] = merkle_root(m_file_piece_hash[fi], pad_hash); + + // files that only have one piece store the piece hash as the + // root, we don't need a pieces layer entry for such files + if (m_file_piece_hash[fi].size() < 2) continue; + auto& pieces = file_pieces[m_fileroots[fi].to_string()].string(); + pieces.clear(); + pieces.reserve(m_file_piece_hash[fi].size() * sha256_hash::size()); + for (auto& p : m_file_piece_hash[fi]) + pieces.append(reinterpret_cast(p.data()), p.size()); + } + } + + 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 (make_v1) + { + 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); + add_file_attrs(info, flags, m_include_symlinks); + if (m_include_symlinks + && (flags & file_storage::flag_symlink)) + { + add_symlink_path(info, m_files.internal_symlink(first)); + } +#if TORRENT_ABI_VERSION < 3 + if (!m_filehashes.empty()) + { + info["sha1"] = m_filehashes[first].to_string(); + } +#endif + } + else + { + 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); + add_file_attrs(file_e, flags, m_include_symlinks); + + if (m_include_symlinks && (flags & file_storage::flag_symlink)) + { + add_symlink_path(file_e, m_files.internal_symlink(i)); + } +#if TORRENT_ABI_VERSION < 3 + if (!m_filehashes.empty() && m_filehashes[i] != sha1_hash()) + { + file_e["sha1"] = m_filehashes[i].to_string(); + } +#endif + } + } + } + + if (make_v2) + { + auto& tree = info["file tree"]; + + for (file_index_t i : m_files.file_range()) + { + if (files().file_flags(i) & file_storage::flag_pad_file) continue; + + entry* file_e_ptr = &tree; + + { + std::string const file_path = m_files.file_path(i); + auto const split = m_multifile + ? lsplit_path(file_path) + : std::pair(file_path, file_path); + TORRENT_ASSERT(split.first == m_files.name()); + + for (auto e = lsplit_path(split.second); + !e.first.empty(); + e = lsplit_path(e.second)) + { + file_e_ptr = &(*file_e_ptr)[e.first]; + if (file_e_ptr->dict().find({}) != file_e_ptr->dict().end()) + { + // path conflict + // there is already a file with this name + // refuse to generate a torrent with such a conflict + aux::throw_ex(errors::torrent_inconsistent_files); + } + } + } + + if (!file_e_ptr->dict().empty()) + { + // path conflict + // there is already a directory with this name + // refuse to generate a torrent with such a conflict + aux::throw_ex(errors::torrent_inconsistent_files); + } + + entry& file_e = (*file_e_ptr)[{}]; + + if (m_include_mtime && m_files.mtime(i)) file_e["mtime"] = m_files.mtime(i); + + file_flags_t const flags = m_files.file_flags(i); + add_file_attrs(file_e, flags, m_include_symlinks); + + if (m_include_symlinks && (flags & file_storage::flag_symlink)) + { + add_symlink_path(file_e, m_files.internal_symlink(i)); + } + else + { + if (m_files.file_size(i) > 0) + file_e["pieces root"] = m_fileroots[i]; + file_e["length"] = m_files.file_size(i); + } + } + info["meta version"] = 2; + } + + info["piece length"] = m_files.piece_length(); + + if (make_v1) + { + 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) + { + if (m_v2_only) + aux::throw_ex(errors::invalid_hash_entry); + + if (m_piece_hash.empty()) + m_piece_hash.resize(m_files.num_pieces()); + + TORRENT_ASSERT_PRECOND(index >= piece_index_t(0)); + TORRENT_ASSERT_PRECOND(index < m_piece_hash.end_index()); + m_piece_hash[index] = h; + } + + void create_torrent::set_hash2(file_index_t file, piece_index_t::diff_type piece, sha256_hash const& h) + { + TORRENT_ASSERT_PRECOND(file >= file_index_t(0)); + TORRENT_ASSERT_PRECOND(file < m_files.end_file()); + TORRENT_ASSERT_PRECOND(piece >= piece_index_t::diff_type(0)); + TORRENT_ASSERT_PRECOND(piece < piece_index_t::diff_type(m_files.file_num_pieces(file))); + TORRENT_ASSERT_PRECOND(!m_files.pad_file_at(file)); + TORRENT_ASSERT_PRECOND(!h.is_all_zeros()); + TORRENT_ASSERT_PRECOND(m_files.file_num_pieces(file) > 0); + + if (m_v1_only) + aux::throw_ex(errors::invalid_hash_entry); + + if (m_file_piece_hash.empty()) + m_file_piece_hash.resize(m_files.num_files()); + + auto& fh = m_file_piece_hash[file]; + if (fh.empty()) + fh.resize(std::size_t(m_files.file_num_pieces(file))); + fh[piece] = h; + } + +#if TORRENT_ABI_VERSION < 3 + 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; + } +#endif + + 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; + } + + void create_torrent::set_creation_date(std::time_t timestamp) + { + m_creation_date = timestamp; + } +} diff --git a/src/directory.cpp b/src/directory.cpp new file mode 100644 index 0000000..ba64041 --- /dev/null +++ b/src/directory.cpp @@ -0,0 +1,126 @@ +/* + +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 +#include "libtorrent/aux_/directory.hpp" +#include "libtorrent/aux_/path.hpp" + +namespace libtorrent { +namespace aux { + + 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 + } + +} // namespace aux +} diff --git a/src/disabled_disk_io.cpp b/src/disabled_disk_io.cpp new file mode 100644 index 0000000..99c20e3 --- /dev/null +++ b/src/disabled_disk_io.cpp @@ -0,0 +1,206 @@ +/* + +Copyright (c) 2016, 2019-2020, Arvid Norberg +Copyright (c) 2017-2018, 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/disabled_disk_io.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/peer_request.hpp" + +#include +#include + +namespace libtorrent { + +// This is a dummy implementation of the disk_interface. It discards any data +// written to it and when reading, it returns zeroes. It is primarily useful for +// testing and benchmarking +struct TORRENT_EXTRA_EXPORT disabled_disk_io final + : disk_interface + , buffer_allocator_interface +{ + disabled_disk_io(io_context& ios, counters&) + : m_zero_buffer(std::make_unique(default_block_size)) + , m_ios(ios) + { + std::memset(m_zero_buffer.get(), 0, default_block_size); + } + + storage_holder new_torrent(storage_params const& + , std::shared_ptr const&) override + { + return storage_holder(storage_index_t(0), *this); + } + + void remove_torrent(storage_index_t) override {} + + void abort(bool) override {} + + void settings_updated() override {} + + void async_read(storage_index_t, peer_request const& r + , std::function handler + , disk_job_flags_t) override + { + TORRENT_ASSERT(r.length <= default_block_size); + TORRENT_UNUSED(r); + + post(m_ios, [this, h = std::move(handler)] { + h(disk_buffer_holder(*this, this->m_zero_buffer.get(), default_block_size) + , storage_error{}); + }); + } + + bool async_write(storage_index_t + , peer_request const& r + , char const*, std::shared_ptr + , std::function handler + , disk_job_flags_t) override + { + TORRENT_ASSERT(r.length <= default_block_size); + TORRENT_UNUSED(r); + + post(m_ios, [h = std::move(handler)] { h(storage_error{}); }); + return false; + } + + void async_hash(storage_index_t + , piece_index_t piece, span, disk_job_flags_t + , std::function handler) override + { + // TODO: it would be nice to return a valid hash of zeroes here + post(m_ios, [h = std::move(handler), piece] { h(piece, sha1_hash{}, storage_error{}); }); + } + + void async_hash2(storage_index_t, piece_index_t piece, int + , disk_job_flags_t + , std::function handler) override + { + post(m_ios, [h = std::move(handler), piece]() { h(piece, sha256_hash{}, storage_error{}); }); + } + + void async_move_storage(storage_index_t + , std::string p, move_flags_t + , std::function handler) override + { + post(m_ios, [h = std::move(handler), path = std::move(p)] () mutable + { h(status_t::no_error, std::move(path), storage_error{}); }); + } + + void async_release_files(storage_index_t, std::function handler) override + { + post(m_ios, [h = std::move(handler)] { h(); }); + } + + void async_delete_files(storage_index_t + , remove_flags_t, std::function handler) override + { + post(m_ios, [h = std::move(handler)] { h(storage_error{}); }); + } + + void async_check_files(storage_index_t + , add_torrent_params const* + , aux::vector + , std::function handler) override + { + post(m_ios, [h = std::move(handler)] { h(status_t::no_error, storage_error{}); }); + } + + void async_rename_file(storage_index_t + , file_index_t index, std::string name + , std::function handler) override + { + post(m_ios, [h = std::move(handler), index, n = std::move(name)] () mutable + { h(std::move(n), index, storage_error{}); }); + } + + void async_stop_torrent(storage_index_t, std::function handler) override + { + post(m_ios, [h = std::move(handler)] { h(); }); + } + + void async_set_file_priority(storage_index_t + , aux::vector prio + , std::function)> handler) override + { + post(m_ios, [h = std::move(handler), p = std::move(prio)] () mutable + { h(storage_error{}, std::move(p)); }); + } + + void async_clear_piece(storage_index_t + , piece_index_t const index, std::function handler) override + { + post(m_ios, [h = std::move(handler), index] { h(index); }); + } + + void update_stats_counters(counters& c) const override + { + c.set_value(counters::disk_blocks_in_use, 1); + } + + // implements buffer_allocator_interface + // since we just have a single zeroed buffer, we don't need to free anything + // here. The buffer is owned by the disabled_disk_io object itself + void free_disk_buffer(char*) override {} + + std::vector get_status(storage_index_t) const override + { return {}; } + + // this submits all queued up jobs to the thread + void submit_jobs() override {} + +private: + + // this is the one buffer of zeroes we hand back to all read jobs + std::unique_ptr m_zero_buffer; + + // this is the main thread io_context. Callbacks are + // posted on this in order to have them execute in + // the main thread. + io_context& m_ios; +}; + +std::unique_ptr disabled_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt) +{ + return std::make_unique(ios, cnt); +} + +} + diff --git a/src/disk_buffer_holder.cpp b/src/disk_buffer_holder.cpp new file mode 100644 index 0000000..01b3ea2 --- /dev/null +++ b/src/disk_buffer_holder.cpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2008, 2014, 2016-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 "libtorrent/disk_buffer_holder.hpp" +#include + +namespace libtorrent { + + disk_buffer_holder::disk_buffer_holder(buffer_allocator_interface& alloc + , char* const buf, int const sz) noexcept + : m_allocator(&alloc), m_buf(buf), m_size(sz) + {} + + 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) + { + h.m_buf = nullptr; + h.m_size = 0; + } + + 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; + } + + void disk_buffer_holder::reset() + { + if (m_buf) m_allocator->free_disk_buffer(m_buf); + m_buf = nullptr; + m_size = 0; + } + + 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..f0cd3af --- /dev/null +++ b/src/disk_buffer_pool.cpp @@ -0,0 +1,235 @@ +/* + +Copyright (c) 2011, 2013-2020, Arvid Norberg +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016, 2018, 2020, 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_/disk_buffer_pool.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/settings_pack.hpp" // for settings_interface +#include "libtorrent/io_context.hpp" +#include "libtorrent/disk_observer.hpp" +#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 aux { + +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_context& ios) + : m_in_use(0) + , m_max_use(64) + , m_low_watermark(std::max(m_max_use - 32, 0)) + , m_exceeded_max_size(false) + , m_ios(ios) + {} + + disk_buffer_pool::~disk_buffer_pool() + { + TORRENT_ASSERT(m_magic == 0x1337); +#if TORRENT_USE_ASSERTS + m_magic = 0; +#endif + } + + // 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(); + post(m_ios, std::bind(&watermark_callback, std::move(cbs))); + } + + 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; + } + + 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; + 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; + } + + 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) + { + 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); + remove_buffer_in_use(buf); + free_buffer_impl(buf, l); + check_buffer_level(l); + } + + void disk_buffer_pool::set_settings(settings_interface const& sett) + { + std::unique_lock l(m_pool_mutex); + + int const pool_size = std::max(1, sett.get_int(settings_pack::max_queued_disk_bytes) / default_block_size); + m_max_use = pool_size; + m_low_watermark = m_max_use / 2; + if (m_in_use >= m_max_use && !m_exceeded_max_size) + { + m_exceeded_max_size = true; + } + +#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_interface.cpp b/src/disk_interface.cpp new file mode 100644 index 0000000..39ba2b8 --- /dev/null +++ b/src/disk_interface.cpp @@ -0,0 +1,44 @@ +/* + +Copyright (c) 2018, Steven Siloti +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 "libtorrent/disk_interface.hpp" + +namespace libtorrent { + +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::v1_hash; +constexpr disk_job_flags_t disk_interface::flush_piece; + +} diff --git a/src/disk_io_thread_pool.cpp b/src/disk_io_thread_pool.cpp new file mode 100644 index 0000000..42c10f3 --- /dev/null +++ b/src/disk_io_thread_pool.cpp @@ -0,0 +1,209 @@ +/* + +Copyright (c) 2016, 2018, Steven Siloti +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2017-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/aux_/disk_io_thread_pool.hpp" +#include "libtorrent/assert.hpp" + +#include + +namespace { + + constexpr std::chrono::seconds reap_idle_threads_interval(60); +} + +namespace libtorrent { +namespace aux { + + disk_io_thread_pool::disk_io_thread_pool(pool_thread_interface& thread_iface + , io_context& 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) + , m_ioc(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_after(reap_idle_threads_interval); + m_idle_timer.async_wait([this](error_code const& ec) { reap_idle_threads(ec); }); + } + + // work keeps the io_context::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_context 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) + , make_work_guard(m_ioc)); + } + } + + 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_after(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..a8cf0d5 --- /dev/null +++ b/src/disk_job_fence.cpp @@ -0,0 +1,220 @@ +/* + +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2016-2020, Arvid Norberg +Copyright (c) 2017, 2020, 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/aux_/disk_job_fence.hpp" +#include "libtorrent/aux_/mmap_disk_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(mmap_disk_job* j, tailqueue& jobs) + { + std::lock_guard l(m_mutex); + + TORRENT_ASSERT(j->flags & mmap_disk_job::in_progress); + j->flags &= ~mmap_disk_job::in_progress; + + TORRENT_ASSERT(m_outstanding_jobs > 0); + --m_outstanding_jobs; + if (j->flags & mmap_disk_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()) + { + mmap_disk_job *bj = m_blocked_jobs.pop_front(); + if (bj->flags & mmap_disk_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 & mmap_disk_job::in_progress)); + bj->flags |= mmap_disk_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 & mmap_disk_job::in_progress)); + bj->flags |= mmap_disk_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 + mmap_disk_job *bj = m_blocked_jobs.pop_front(); + TORRENT_ASSERT(bj->flags & mmap_disk_job::fence); + + TORRENT_ASSERT(!(bj->flags & mmap_disk_job::in_progress)); + bj->flags |= mmap_disk_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(mmap_disk_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 & mmap_disk_job::in_progress)); + j->flags |= mmap_disk_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 + int disk_job_fence::raise_fence(mmap_disk_job* j, counters& cnt) + { + TORRENT_ASSERT(!(j->flags & mmap_disk_job::in_progress)); + TORRENT_ASSERT(!(j->flags & mmap_disk_job::fence)); + j->flags |= mmap_disk_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 + + j->flags |= mmap_disk_job::in_progress; + ++m_outstanding_jobs; + return fence_post_fence; + } + + ++m_has_fence; +#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 fence_post_none; + } + +} +} diff --git a/src/disk_job_pool.cpp b/src/disk_job_pool.cpp new file mode 100644 index 0000000..347067e --- /dev/null +++ b/src/disk_job_pool.cpp @@ -0,0 +1,111 @@ +/* + +Copyright (c) 2014, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2018, 2020, 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/aux_/disk_job_pool.hpp" +#include "libtorrent/aux_/mmap_disk_job.hpp" + +namespace libtorrent { +namespace aux { + + disk_job_pool::disk_job_pool() + : m_jobs_in_use(0) + , m_read_jobs(0) + , m_write_jobs(0) + , m_job_pool(sizeof(mmap_disk_job)) + {} + + disk_job_pool::~disk_job_pool() + { +// #error this should be fixed! +// TORRENT_ASSERT(m_jobs_in_use == 0); + } + + mmap_disk_job* disk_job_pool::allocate_job(job_action_t const type) + { + std::unique_lock l(m_job_mutex); + void* storage = m_job_pool.malloc(); + m_job_pool.set_next_size(100); + ++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(storage); + + auto ptr = new (storage) mmap_disk_job; + ptr->action = type; +#if TORRENT_USE_ASSERTS + ptr->in_use = true; +#endif + return ptr; + } + + void disk_job_pool::free_job(mmap_disk_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->~mmap_disk_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(mmap_disk_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]->~mmap_disk_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/ed25519/LICENSE b/src/ed25519/LICENSE new file mode 100644 index 0000000..54b2e00 --- /dev/null +++ b/src/ed25519/LICENSE @@ -0,0 +1,22 @@ +Derived from https://ed25519.cr.yp.to/software.html which is public domain + +Additional contributions are placed in the public domain by +Arvid Norberg + +Copyright (c) 2015 Orson Peters + +This software is provided 'as-is', without any express or implied warranty. In no event will the +authors 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. + diff --git a/src/ed25519/add_scalar.cpp b/src/ed25519/add_scalar.cpp new file mode 100644 index 0000000..553a2ef --- /dev/null +++ b/src/ed25519/add_scalar.cpp @@ -0,0 +1,75 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/aux_/ed25519.hpp" +#include "libtorrent/aux_/hasher512.hpp" +#include "ge.h" +#include "sc.h" + +namespace libtorrent { +namespace aux { + +/* 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/src/ed25519/fe.cpp b/src/ed25519/fe.cpp new file mode 100644 index 0000000..a979ef4 --- /dev/null +++ b/src/ed25519/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/src/ed25519/fe.h b/src/ed25519/fe.h new file mode 100644 index 0000000..fbe47dc --- /dev/null +++ b/src/ed25519/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/src/ed25519/fixedint.h b/src/ed25519/fixedint.h new file mode 100644 index 0000000..abb5a37 --- /dev/null +++ b/src/ed25519/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/src/ed25519/ge.cpp b/src/ed25519/ge.cpp new file mode 100644 index 0000000..704877b --- /dev/null +++ b/src/ed25519/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/src/ed25519/ge.h b/src/ed25519/ge.h new file mode 100644 index 0000000..17fde2d --- /dev/null +++ b/src/ed25519/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/src/ed25519/hasher512.cpp b/src/ed25519/hasher512.cpp new file mode 100644 index 0000000..d61e24d --- /dev/null +++ b/src/ed25519/hasher512.cpp @@ -0,0 +1,152 @@ +/* + +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/aux_/hasher512.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/ssl.hpp" + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + +namespace libtorrent { +namespace aux { + + 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_CNG +#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_CNG + m_context.update(data); +#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_CNG + m_context.get_hash(digest.data(), digest.size()); +#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_CNG + m_context.reset(); +#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 + +} +} + +#include "libtorrent/aux_/disable_warnings_pop.hpp" diff --git a/src/ed25519/key_exchange.cpp b/src/ed25519/key_exchange.cpp new file mode 100644 index 0000000..e35b4b0 --- /dev/null +++ b/src/ed25519/key_exchange.cpp @@ -0,0 +1,88 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/aux_/ed25519.hpp" +#include "fe.h" + +namespace libtorrent { +namespace aux { + +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/src/ed25519/keypair.cpp b/src/ed25519/keypair.cpp new file mode 100644 index 0000000..4b56aa9 --- /dev/null +++ b/src/ed25519/keypair.cpp @@ -0,0 +1,24 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/aux_/ed25519.hpp" +#include "libtorrent/aux_/hasher512.hpp" +#include "ge.h" + +namespace libtorrent { +namespace aux { + +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/src/ed25519/precomp_data.h b/src/ed25519/precomp_data.h new file mode 100644 index 0000000..791454f --- /dev/null +++ b/src/ed25519/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/src/ed25519/sc.cpp b/src/ed25519/sc.cpp new file mode 100644 index 0000000..8a18055 --- /dev/null +++ b/src/ed25519/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/src/ed25519/sc.h b/src/ed25519/sc.h new file mode 100644 index 0000000..0c058e5 --- /dev/null +++ b/src/ed25519/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/src/ed25519/sha512.cpp b/src/ed25519/sha512.cpp new file mode 100644 index 0000000..673b921 --- /dev/null +++ b/src/ed25519/sha512.cpp @@ -0,0 +1,291 @@ +#include +#include + +#include "libtorrent/aux_/sha512.hpp" + +#if !defined TORRENT_USE_LIBGCRYPT \ + && !TORRENT_USE_COMMONCRYPTO \ + && !TORRENT_USE_CNG \ + && !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 { +namespace aux { + +/* 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; +} +} // aux namespace +} // libtorrent namespace + +#endif diff --git a/src/ed25519/sign.cpp b/src/ed25519/sign.cpp new file mode 100644 index 0000000..a6886bc --- /dev/null +++ b/src/ed25519/sign.cpp @@ -0,0 +1,37 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/aux_/ed25519.hpp" +#include "libtorrent/aux_/hasher512.hpp" +#include "ge.h" +#include "sc.h" + +namespace libtorrent { +namespace aux { + +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/src/ed25519/verify.cpp b/src/ed25519/verify.cpp new file mode 100644 index 0000000..43d1097 --- /dev/null +++ b/src/ed25519/verify.cpp @@ -0,0 +1,84 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/aux_/ed25519.hpp" +#include "libtorrent/aux_/hasher512.hpp" +#include "ge.h" +#include "sc.h" + +namespace libtorrent { +namespace aux { + +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/src/entry.cpp b/src/entry.cpp new file mode 100644 index 0000000..fb7f34e --- /dev/null +++ b/src/entry.cpp @@ -0,0 +1,774 @@ +/* + +Copyright (c) 2003-2008, 2010, 2014-2020, Arvid Norberg +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2019, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/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 aux { + + string_view integer_to_str(std::array& buf, entry::integer_type val) + { + if (val >= 0) + { + if (val < 10) + { + buf[0] = '0' + static_cast(val); + return {buf.data(), std::size_t(1)}; + } + if (val < 100) + { + buf[0] = '0' + (val / 10) % 10; + buf[1] = '0' + val % 10; + return {buf.data(), std::size_t(2)}; + } + if (val < 1000) + { + buf[0] = '0' + (val / 100) % 10; + buf[1] = '0' + (val / 10) % 10; + buf[2] = '0' + val % 10; + return {buf.data(), std::size_t(3)}; + } + if (val < 10000) + { + buf[0] = '0' + (val / 1000) % 10; + buf[1] = '0' + (val / 100) % 10; + buf[2] = '0' + (val / 10) % 10; + buf[3] = '0' + val % 10; + return {buf.data(), std::size_t(4)}; + } + if (val < 100000) + { + buf[0] = '0' + (val / 10000) % 10; + buf[1] = '0' + (val / 1000) % 10; + buf[2] = '0' + (val / 100) % 10; + buf[3] = '0' + (val / 10) % 10; + buf[4] = '0' + val % 10; + return {buf.data(), std::size_t(5)}; + } + } + // slow path + // convert positive values to negative, since the negative space is + // larger, so we can fit INT64_MIN + int sign = 1; + if (val >= 0) + { + sign = 0; + val = -val; + } + char* ptr = &buf.back(); + if (val == 0) *ptr-- = '0'; + while (val != 0) + { + *ptr-- = '0' - char(val % 10); + val /= 10; + } + if (sign) *ptr-- = '-'; + ++ptr; + return {ptr, static_cast(&buf.back() - ptr + 1)}; + } +} // aux + +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) + { + // at least GCC-5.4 for ARM (on travis) has a libstdc++ whose debug map$ + // doesn't seem to support transparent comparators$ +#if ! defined _GLIBCXX_DEBUG + auto const i = dict().find(key); +#else + auto const i = dict().find(std::string(key)); +#endif + 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 + { + // at least GCC-5.4 for ARM (on travis) has a libstdc++ whose debug map$ + // doesn't seem to support transparent comparators$ +#if ! defined _GLIBCXX_DEBUG + auto const i = dict().find(key); +#else + auto const i = dict().find(std::string(key)); +#endif + if (i == dict().end()) throw_error(); + return i->second; + } + + entry* entry::find_key(string_view key) + { +#if ! defined _GLIBCXX_DEBUG + auto const i = dict().find(key); +#else + auto const i = dict().find(std::string(key)); +#endif + if (i == dict().end()) return nullptr; + return &i->second; + } + + entry const* entry::find_key(string_view key) const + { +#if ! defined _GLIBCXX_DEBUG + auto const i = dict().find(key); +#else + auto const i = dict().find(std::string(key)); +#endif + 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; + } + + 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..96195b6 --- /dev/null +++ b/src/enum_net.cpp @@ -0,0 +1,1538 @@ +/* + +Copyright (c) 2007-2012, 2014-2020, Arvid Norberg +Copyright (c) 2015-2018, Alden Torres +Copyright (c) 2015-2018, 2020, Steven Siloti +Copyright (c) 2016-2017, Andrei Kurushin +Copyright (c) 2018, Alexandre Janniaux +Copyright (c) 2020, Tiger Wang +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/assert.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/ip_helpers.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_GRTTABLE +#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 +#ifdef TORRENT_WINRT +#include +#endif +#endif + +#if TORRENT_USE_NETLINK + +// We really should be including here, for the IF_OPER_* flags. +// However, 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 +#ifdef TORRENT_BEOS +// TODO: in C++17, use __has_include for this. Other operating systems are +// likely to require this as well +#include +#endif +// 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 { + +#if !defined TORRENT_WINDOWS && !defined TORRENT_BUILD_SIMULATOR + 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{}) +#ifdef IFF_RUNNING + | ((f & IFF_RUNNING) ? if_flags::running : interface_flags{}) +#endif + | ((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 + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-compare" +#pragma clang diagnostic ignored "-Wcast-qual" +#pragma clang diagnostic ignored "-Wcast-align" +#endif + // these are here to concentrate all the shady casts these macros expand to, + // to disable the warnings for them all + bool nlmsg_ok(nlmsghdr const* hdr, int const len) + { return NLMSG_OK(hdr, len); } + + nlmsghdr const* nlmsg_next(nlmsghdr const* hdr, int& len) + { return NLMSG_NEXT(hdr, len); } + + void const* nlmsg_data(nlmsghdr const* hdr) + { return NLMSG_DATA(hdr); } + + rtattr const* rtm_rta(rtmsg const* hdr) + { return static_cast(RTM_RTA(hdr)); } + + std::size_t rtm_payload(nlmsghdr const* hdr) + { return RTM_PAYLOAD(hdr); } + + bool rta_ok(rtattr const* rt, std::size_t const len) + { return RTA_OK(rt, len); } + + void const* rta_data(rtattr const* rt) + { return RTA_DATA(rt); } + + rtattr const* rta_next(rtattr const* rt, std::size_t& len) + { return RTA_NEXT(rt, len); } + + rtattr const* ifa_rta(ifaddrmsg const* ifa) + { return static_cast(IFA_RTA(ifa)); } + + std::size_t ifa_payload(nlmsghdr const* hdr) + { return IFA_PAYLOAD(hdr); } + + rtattr const* ifla_rta(ifinfomsg const* ifinfo) + { return static_cast(IFLA_RTA(ifinfo)); } + + std::size_t ifla_payload(nlmsghdr const* hdr) + { return IFLA_PAYLOAD(hdr); } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + 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; + + auto 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)) + { + // 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; + // 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; + int 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 = static_cast(nlmsg_data(nl_hdr)); + rtattr const* rta_ptr = ifla_rta(if_msg); + std::size_t 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: std::memcpy(&ret.mtu, ptr, sizeof(int)); break; + case IFLA_LINK: std::memcpy(&ret.type, ptr, sizeof(int)); break; + case IFLA_OPERSTATE: std::memcpy(&ret.oper_state, ptr, sizeof(int)); break; + + // ignore these attributes + case IFLA_ADDRESS: + case IFLA_BROADCAST: + case IFLA_QDISC: + case IFLA_COST: + case IFLA_WIRELESS: + case IFLA_WEIGHT: + case IFLA_LINKMODE: + case IFLA_LINKINFO: + 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; + + auto const* rt_msg = static_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; + std::size_t rt_len = rtm_payload(nl_hdr); + for (rtattr const* rt_attr = 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: + std::memcpy(&if_index, rta_data(rt_attr), sizeof(int)); + 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; + } + } + + 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; + + auto const* addr_msg = static_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 == int(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(); + std::size_t rt_len = ifa_payload(nl_hdr); + for (rtattr const* rt_attr = 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; + } + } + + static_assert(sizeof(ip_info->name) == sizeof(interface->name), "interface name field sizes differ"); + std::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) + { + // some android ifaddrs implementations are buggy + if (ifa->ifa_addr == nullptr) return false; + + // determine address + rv.interface_address = sockaddr_to_address(ifa->ifa_addr); + if (rv.interface_address.is_unspecified()) return false; + + if (ifa->ifa_name != nullptr) + { + 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_uint() & mask.to_v4().to_uint()) + == (a2.to_v4().to_uint() & mask.to_v4().to_uint()); + } + + std::vector enum_net_interfaces(io_context& 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 = make_address_v4("255.0.0.0"); + else + wan.netmask = make_address_v6("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 + std::uint32_t 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, solaris and Haiku +#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 const* ifr = reinterpret_cast(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) + iface.flags = convert_if_flags(req.ifr_flags); + else + iface.flags = if_flags::up; + + 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 != nullptr; 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, nullptr, 0, buffer, + sizeof(buffer), &size, nullptr, nullptr) != 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 && aux::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_any_internet_route(span routes) + { + return std::any_of(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 r.destination.is_unspecified() + || aux::is_global(r.destination) ; + }); + } + + bool has_internet_route(string_view device, int const fam, span routes) + { + return std::any_of(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() + || aux::is_global(r.destination) + ); + }); + } + + std::vector enum_routes(io_context& 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 = make_address_v4("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 = make_address_v6("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 = make_address(adapter->IpAddressList.IpAddress.String, ec); + r.gateway = make_address(adapter->GatewayList.IpAddress.String, ec); + r.netmask = make_address(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(); + } +#elif TORRENT_USE_GRTTABLE + + for (int fam = 0; fam < 2; ++fam) + { + int const s = ::socket(fam == 0 ? AF_INET : AF_INET6, SOCK_DGRAM, 0); + if (s < 0) + { + ec = error_code(errno, system_category()); + return ret; + } + socket_closer c1(s); + ifconf ifc; + ifc.ifc_len = sizeof(ifc.ifc_value); + if (ioctl(s, SIOCGRTSIZE, &ifc, sizeof(ifc)) < 0) + { + ec = error_code(errno, system_category()); + return ret; + } + + std::uint32_t const sz(ifc.ifc_value); + + std::vector buf(sz); + ifc.ifc_len = sz; + ifc.ifc_buf = static_cast(buf.data()); + if (ioctl(s, SIOCGRTTABLE, &ifc, sizeof(ifc)) < 0) + { + ec = error_code(errno, system_category()); + return ret; + } + + ifreq const* i = reinterpret_cast(ifc.ifc_buf); + + int bytes_left = static_cast(ifc.ifc_len); + while (bytes_left > 0) + { + route_entry const& route = i->ifr_route; + + ip_route ipr{}; + int skip = IF_NAMESIZE + sizeof(route_entry); + if (route.destination != nullptr) + { + ipr.destination = sockaddr_to_address(route.destination); + skip += route.destination->sa_len; + } + if (route.mask != nullptr) + { + ipr.netmask = sockaddr_to_address(route.mask); + skip += route.mask->sa_len; + } + if (route.gateway) + { + ipr.gateway = sockaddr_to_address(route.gateway); + skip += route.gateway->sa_len; + } + if (route.source != nullptr) + { + ipr.source_hint = sockaddr_to_address(route.source); + skip += route.source->sa_len; + } + ipr.mtu = route.mtu; + std::strncpy(ipr.name, i->ifr_name, sizeof(ipr.name) - 1); + ipr.name[sizeof(ipr.name) - 1] = '\0'; + + ret.push_back(ipr); + bytes_left -= skip; + i = reinterpret_cast(reinterpret_cast(i) + skip); + } + } +#elif defined TORRENT_ANDROID && __ANDROID_API__ >= 24 + ec = boost::asio::error::operation_not_supported; +#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_context& 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..9c16b90 --- /dev/null +++ b/src/error_code.cpp @@ -0,0 +1,371 @@ +/* + +Copyright (c) 2008-2020, Arvid Norberg +Copyright (c) 2016-2017, 2019, Steven Siloti +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2019, 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/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 {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", + "invalid hash request", + "invalid hashes", + "invalid hash reject", + +// 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", + "skipping tracker announce (unreachable)", + "", + "", + "", + "", + "", + "", + "", + "", + "", + +#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", + "blocked by SSRF mitigation", + "blocked by IDNA ban", + "", + "", + "", + "", + "", + "", + "", + + "the torrent file has an unknown meta version", + "the v2 torrent file has no file tree", + "the torrent contains v2 keys but does not specify meta version 2", + "the v1 and v2 file metadata does not match", + "one or more files are missing piece layer hashes", + "a piece layer is invalid", + "a v2 file entry has no root hash", + "v1 and v2 hashes do not describe the same data", + "a file in the v2 metadata has the pad attribute set" + }; + 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 {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 {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..0f83ca5 --- /dev/null +++ b/src/escape_string.cpp @@ -0,0 +1,586 @@ +/* + +Copyright (c) 2004, 2006-2007, 2009-2011, 2013, 2015-2020, Arvid Norberg +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016-2017, Andrei Kurushin +Copyright (c) 2016-2017, 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 +#include +#include +#include +#include + +#ifdef TORRENT_WINDOWS +#include "libtorrent/aux_/windows.hpp" +#else +#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(), '\\', '/'); + } + + // 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; + } + + 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_NATIVE_UTF8 + +#if defined TORRENT_WINDOWS + +namespace { + + std::string convert_impl(std::string const& s, UINT from, UINT to) + { + // if the local codepage is already UTF-8, no need to convert + static UINT const cp = GetACP(); + if (cp == CP_UTF8) return s; + + 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); + } + +#else + +namespace { + + bool ends_with(string_view s, string_view suffix) + { + return s.size() >= suffix.size() + && s.substr(s.size() - suffix.size()) == suffix; + } + + bool has_utf8_locale() + { + char const* lang = std::getenv("LANG"); + if (lang == nullptr) return false; + return ends_with(lang, ".UTF-8"); + } + + bool need_conversion() + { + static bool const ret = has_utf8_locale(); + return !ret; + } +} + + std::string convert_to_native(std::string const& s) + { + if (!need_conversion()) return 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]; + std::size_t const size = std::wcrtomb(out, static_cast(codepoint), &state); + if (size == static_cast(-1)) + { + ret += '.'; + state = std::mbstate_t{}; + } + else + for (std::size_t i = 0; i < size; ++i) + ret += out[i]; + } + return ret; + } + + std::string convert_from_native(std::string const& s) + { + if (!need_conversion()) return s; + + std::mbstate_t state{}; + std::string ret; + string_view ptr = s; + while (!ptr.empty()) + { + wchar_t codepoint; + std::size_t const size = std::mbrtowc(&codepoint, ptr.data(), ptr.size(), &state); + if (size == static_cast(-1)) + { + ret.push_back('.'); + state = std::mbstate_t{}; + ptr = ptr.substr(1); + } + else + { + append_utf8_codepoint(ret, static_cast(codepoint)); + ptr = ptr.substr(size < 1 ? 1 : size); + } + } + + return ret; + } + +#endif +#endif + +} diff --git a/src/ffs.cpp b/src/ffs.cpp new file mode 100644 index 0000000..af4e7eb --- /dev/null +++ b/src/ffs.cpp @@ -0,0 +1,185 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2017-2019, Arvid Norberg +Copyright (c) 2018, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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..105c0fd --- /dev/null +++ b/src/file.cpp @@ -0,0 +1,331 @@ +/* + +Copyright (c) 2018, d-komarov +Copyright (c) 2004-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2016-2017, Steven Siloti +Copyright (c) 2016-2017, Andrei Kurushin +Copyright (c) 2020, Tiger Wang +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 + +#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/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_/open_mode.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#ifdef TORRENT_WINDOWS +// windows part + +#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 +#endif + +#endif + +#endif // posix part + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifndef INVALID_HANDLE_VALUE +#define INVALID_HANDLE_VALUE (-1) +#endif + +namespace libtorrent { + +#ifdef TORRENT_WINDOWS +namespace { + std::int64_t pread(HANDLE fd, void* data, std::size_t len, std::int64_t const offset) + { + OVERLAPPED ol{}; + ol.Offset = offset & 0xffffffff; + ol.OffsetHigh = offset >> 32; + DWORD bytes_read = 0; + if (ReadFile(fd, data, DWORD(len), &bytes_read, &ol) == FALSE) + { + if (GetLastError() == ERROR_HANDLE_EOF) return 0; + return -1; + } + + return bytes_read; + } + + std::int64_t pwrite(HANDLE fd, void const* data, std::size_t len, std::int64_t const offset) + { + OVERLAPPED ol{}; + ol.Offset = offset & 0xffffffff; + ol.OffsetHigh = offset >> 32; + DWORD bytes_written = 0; + if (WriteFile(fd, data, DWORD(len), &bytes_written, &ol) == FALSE) + return -1; + + return bytes_written; + } +} +#endif + + file::file() : m_file_handle(INVALID_HANDLE_VALUE) {} + + file::file(file&& f) noexcept + : m_file_handle(f.m_file_handle) + { + 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; + f.m_file_handle = INVALID_HANDLE_VALUE; + return *this; + } + + file::~file() + { + if (m_file_handle == INVALID_HANDLE_VALUE) return; + +#ifdef TORRENT_WINDOWS + CloseHandle(m_file_handle); +#else + if (m_file_handle != INVALID_HANDLE_VALUE) + ::close(m_file_handle); +#endif + + m_file_handle = INVALID_HANDLE_VALUE; + } + + file::file(std::string const& path, aux::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 + native_path_string file_path = convert_to_native_path_string(path); + +#ifdef TORRENT_WINDOWS +#ifdef TORRENT_WINRT + + const auto handle = CreateFile2(file_path.c_str() + , (mode & aux::open_mode::write) ? GENERIC_WRITE | GENERIC_READ : GENERIC_READ + , FILE_SHARE_READ | FILE_SHARE_WRITE + , (mode & aux::open_mode::write) ? OPEN_ALWAYS : OPEN_EXISTING + , nullptr); + +#else + + handle_type handle = CreateFileW(file_path.c_str() + , (mode & aux::open_mode::write) ? GENERIC_WRITE | GENERIC_READ : GENERIC_READ + , FILE_SHARE_READ | FILE_SHARE_WRITE + , nullptr + , (mode & aux::open_mode::write) ? OPEN_ALWAYS : OPEN_EXISTING + , (mode & aux::open_mode::hidden) ? FILE_ATTRIBUTE_HIDDEN : 0 + , nullptr); + +#endif + + if (handle == INVALID_HANDLE_VALUE) + { + ec.assign(GetLastError(), system_category()); + TORRENT_ASSERT(ec); + return; + } + + m_file_handle = handle; +#else // TORRENT_WINDOWS + + handle_type handle = ::open(file_path.c_str() + , ((mode & aux::open_mode::write) ? O_RDWR | O_CREAT : O_RDONLY) +#ifdef O_BINARY + | O_BINARY +#endif + , S_IRUSR | S_IWUSR + | S_IRGRP | S_IWGRP + | S_IROTH | S_IWOTH); + + if (handle == INVALID_HANDLE_VALUE) + { + ec.assign(errno, system_category()); + TORRENT_ASSERT(ec); + return; + } + + m_file_handle = handle; +#endif + + TORRENT_ASSERT(m_file_handle != INVALID_HANDLE_VALUE); + } + +namespace { + + template + std::int64_t iov(Fun f, handle_type fd, std::int64_t file_offset + , span bufs, error_code& ec) + { + 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; + } + +} // 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, aux::open_mode_t) + { + 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(!bufs.empty()); + TORRENT_ASSERT(m_file_handle != INVALID_HANDLE_VALUE); + + return iov(&pread, m_file_handle, file_offset, bufs, ec); + } + + // 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, aux::open_mode_t) + { + 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(!bufs.empty()); + TORRENT_ASSERT(m_file_handle != INVALID_HANDLE_VALUE); + + ec.clear(); + + std::int64_t ret = iov(&pwrite, m_file_handle, file_offset, bufs, ec); + + return ret; + } +} diff --git a/src/file_progress.cpp b/src/file_progress.cpp new file mode 100644 index 0000000..525c763 --- /dev/null +++ b/src/file_progress.cpp @@ -0,0 +1,208 @@ +/* + +Copyright (c) 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016-2017, 2019, 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/piece_picker.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/aux_/file_progress.hpp" +#include "libtorrent/aux_/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); + m_pad_file.clear(); + m_pad_file.reserve(num_files); + for (file_index_t i : fs.file_range()) + { + m_file_sizes.push_back(fs.file_size(i)); + m_pad_file.push_back(fs.pad_file_at(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); + + if (!fs.pad_file_at(file_index)) + m_total_on_disk += add; + + 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_total_on_disk = 0; + 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); + + bool const is_pad_file = fs.pad_file_at(file_index); + if (!is_pad_file) + m_total_on_disk += add; + + 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 (!is_pad_file) + 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()) + { + TORRENT_ASSERT(m_total_on_disk == 0); + return; + } + + file_index_t index(0); + std::int64_t total_on_disk = 0; + for (std::int64_t progress : m_file_progress) + { + total_on_disk += m_pad_file[index] ? 0 : progress; + TORRENT_ASSERT(progress <= m_file_sizes[index]); + ++index; + } + TORRENT_ASSERT(m_total_on_disk == total_on_disk); + } +#endif +} } diff --git a/src/file_storage.cpp b/src/file_storage.cpp new file mode 100644 index 0000000..50001ee --- /dev/null +++ b/src/file_storage.cpp @@ -0,0 +1,1579 @@ +/* + +Copyright (c) 2008-2020, Arvid Norberg +Copyright (c) 2009, Georg Rudoy +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2017-2019, 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/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/disk_interface.hpp" // for default_block_size +#include "libtorrent/aux_/merkle.hpp" +#include "libtorrent/aux_/throw.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include +#include +#include +#include +#include + +#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() = default; + 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; + file_storage& file_storage::operator=(file_storage&&) & = 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(); + } + +constexpr aux::path_index_t aux::file_entry::no_path; +constexpr aux::path_index_t aux::file_entry::path_is_absolute; + +namespace { + + bool compare_file_offset(aux::file_entry const& lhs + , aux::file_entry const& rhs) + { + return lhs.offset < rhs.offset; + } + +} + + int file_storage::piece_size2(piece_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= piece_index_t{} && index < end_piece()); + TORRENT_ASSERT(max_file_offset / piece_length() > static_cast(index)); + // find the file iterator and file offset + aux::file_entry target; + TORRENT_ASSERT(max_file_offset / piece_length() > static_cast(index)); + target.offset = aux::numeric_cast(std::int64_t(piece_length()) * static_cast(index)); + TORRENT_ASSERT(!compare_file_offset(target, m_files.front())); + + auto const file_iter = std::upper_bound( + m_files.begin(), m_files.end(), target, compare_file_offset); + + TORRENT_ASSERT(file_iter != m_files.begin()); + if (file_iter == m_files.end()) return piece_size(index); + + // this static cast is safe because the resulting value is capped by + // piece_length(), which fits in an int + return static_cast( + std::min(static_cast(piece_length()), file_iter->offset - target.offset)); + } + + int file_storage::blocks_in_piece2(piece_index_t const index) const + { + // the number of default_block_size in a piece size, rounding up + return (piece_size2(index) + default_block_size - 1) / default_block_size; + } + + // 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(aux::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 = aux::file_entry::path_is_absolute; + 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 = aux::file_entry::no_path; + 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); + } + + aux::path_index_t 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 + auto const ret = m_paths.end_index(); + TORRENT_ASSERT(path.size() == 0 || path[0] != '/'); + m_paths.emplace_back(path.data(), path.size()); + return ret; + } + else + { + // yes we do. use it + return aux::path_index_t{aux::numeric_cast( + 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 + +namespace aux { + + file_entry::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) + {} + + file_entry::~file_entry() + { + if (name_len == name_is_owned) delete[] name; + } + + file_entry::file_entry(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) + , root(fe.root) + , path_index(fe.path_index) + { + bool const borrow = fe.name_len != name_is_owned; + set_name(fe.filename(), borrow); + } + + file_entry& file_entry::operator=(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; + root = fe.root; + + // 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; + } + + file_entry::file_entry(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) + , root(fe.root) + , path_index(fe.path_index) + { + fe.name_len = 0; + fe.name = nullptr; + } + + file_entry& file_entry::operator=(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; + + if (name_len == name_is_owned) delete[] name; + + name = fe.name; + root = fe.root; + 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 + // file_entry. + void 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 file_entry::filename() const + { + if (name_len != name_is_owned) return {name, std::size_t(name_len)}; + return name ? string_view(name) : string_view(); + } + +} // aux namespace + +#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); + } +#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 + aux::file_entry target; + TORRENT_ASSERT(offset <= max_file_offset); + 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); + TORRENT_ASSERT(offset <= max_file_offset); + // find the file iterator and file offset + aux::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())}; + } + + file_index_t file_storage::file_index_at_piece(piece_index_t const piece) const + { + return file_index_at_offset(static_cast(piece) * std::int64_t(piece_length())); + } + + file_index_t file_storage::file_index_for_root(sha256_hash const& root_hash) const + { + // TODO: maybe it would be nice to have a better index here + for (file_index_t const i : file_range()) + { + if (root(i) == root_hash) return i; + } + return file_index_t{-1}; + } + + piece_index_t file_storage::piece_index_at_file(file_index_t f) const + { + return piece_index_t{aux::numeric_cast(file_offset(f) / piece_length())}; + } + +#if TORRENT_ABI_VERSION <= 2 + 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 == aux::file_entry::name_is_owned) + return -1; + return m_files[index].name_len; + } +#endif + + std::vector file_storage::map_block(piece_index_t const piece + , std::int64_t const offset, std::int64_t size) const + { + TORRENT_ASSERT_PRECOND(piece >= piece_index_t{0}); + TORRENT_ASSERT_PRECOND(piece < end_piece()); + TORRENT_ASSERT_PRECOND(num_files() > 0); + TORRENT_ASSERT_PRECOND(size >= 0); + std::vector ret; + + if (m_files.empty()) return ret; + + // find the file iterator and file offset + aux::file_entry target; + TORRENT_ASSERT(max_file_offset / m_piece_length > static_cast(piece)); + target.offset = aux::numeric_cast(static_cast(piece) * std::int64_t(m_piece_length) + offset); + TORRENT_ASSERT_PRECOND(std::int64_t(target.offset) <= m_total_size - 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) > m_total_size - size) + size = 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 -= 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); + } + + aux::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; + aux::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 != aux::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; + } + +#ifndef BOOST_NO_EXCEPTIONS + void file_storage::add_file(std::string const& path, std::int64_t const file_size + , file_flags_t const file_flags, std::time_t const mtime, string_view const symlink_path + , char const* root_hash) + { + error_code ec; + add_file_borrow(ec, {}, path, file_size, file_flags, nullptr, mtime + , symlink_path, root_hash); + if (ec) aux::throw_ex(ec); + } + + 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 const symlink_path + , char const* root_hash) + { + error_code ec; + add_file_borrow(ec, filename, path, file_size + , file_flags, filehash, mtime, symlink_path, root_hash); + if (ec) aux::throw_ex(ec); + } +#endif // BOOST_NO_EXCEPTIONS + + void file_storage::add_file(error_code& ec, std::string const& path + , std::int64_t const file_size, file_flags_t const file_flags, std::time_t const mtime + , string_view symlink_path, char const* root_hash) + { + add_file_borrow(ec, {}, path, file_size, file_flags, nullptr, mtime + , symlink_path, root_hash); + } + + void file_storage::add_file_borrow(error_code& ec, 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 const symlink_path + , char const* root_hash) + { + TORRENT_ASSERT_PRECOND(file_size >= 0); + TORRENT_ASSERT_PRECOND(!is_complete(filename)); + + if (file_size > max_file_size) + { + ec = make_error_code(boost::system::errc::file_too_large); + return; + } + + if (max_file_offset - m_total_size < file_size) + { + ec = make_error_code(errors::torrent_invalid_length); + return; + } + + if (!filename.empty()) + { + if (filename.size() >= (1 << 12)) + { + ec = make_error_code(boost::system::errc::filename_too_long); + return; + } + } + else if (lt::filename(path).size() >= (1 << 12)) + { + ec = make_error_code(boost::system::errc::filename_too_long); + return; + } + + 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(); + } + + // files without a root_hash are assumed to be v1, except symlinks. They + // don't have a root hash and can be either v1 or v2 + if (symlink_path.empty() && file_size > 0) + { + bool const v2 = (root_hash != nullptr); + // This condition is true of all files we've added so far have been + // symlinks. i.e. this is the first "real" file we're adding. + // or if m_total_size == 0, all files we've added so far have been + // empty (which also are are v1/v2-ambigous) + if (m_files.size() == m_symlinks.size() || m_total_size == 0) + { + m_v2 = v2; + } + else if (m_v2 != v2) + { + // you cannot mix v1 and v2 files when building torrent_storage. Either + // all files are v1 or all files are v2 + ec = m_v2 ? make_error_code(errors::torrent_missing_pieces_root) + : make_error_code(errors::torrent_inconsistent_files); + return; + } + } + + m_files.emplace_back(); + aux::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); + e.root = root_hash; + + 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() < aux::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; + + // when making v2 torrents, pad the end of each file (if necessary) to + // ensure it ends on a piece boundary. + // we do this at the end of files rather in-front of files to conform to + // the BEP52 reference implementation + if (m_v2 && (m_total_size % piece_length()) != 0) + { + auto const pad_size = piece_length() - (m_total_size % piece_length()); + TORRENT_ASSERT(int(pad_size) != piece_length()); + TORRENT_ASSERT(int(pad_size) > 0); + if (m_total_size > max_file_offset - pad_size) + { + ec = make_error_code(errors::torrent_invalid_length); + return; + } + + m_files.emplace_back(); + // e is invalid from here down! + auto& pad = m_files.back(); + pad.size = static_cast(pad_size); + TORRENT_ASSERT(m_total_size <= max_file_offset); + TORRENT_ASSERT(m_total_size > 0); + pad.offset = static_cast(m_total_size); + pad.path_index = get_or_add_path(".pad"); + char name[30]; + std::snprintf(name, sizeof(name), "%" PRIu64 + , pad.size); + pad.set_name(name); + pad.pad_file = true; + m_total_size += pad_size; + } + } + + // this is here for backwards compatibility with hybrid torrents created + // with libtorrent 2.0.0-2.0.3, which would not add tail-padding + void file_storage::remove_tail_padding() + { + file_index_t f = end_file(); + while (f > file_index_t{0}) + { + --f; + // empty files and symlinks are skipped + if (file_size(f) == 0) continue; + if (pad_file_at(f)) + { + m_total_size -= file_size(f); + m_files.erase(m_files.begin() + int(f)); + while (f < end_file()) + { + m_files[f].offset = static_cast(m_total_size); + TORRENT_ASSERT(m_files[f].size == 0); + ++f; + } + } + // if the last non-empty file isn't a pad file, don't do anything + return; + } + // nothing found + } + + sha1_hash file_storage::hash(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t{} && index < end_file()); + if (index >= m_file_hashes.end_index()) return sha1_hash(); + return sha1_hash(m_file_hashes[index]); + } + + sha256_hash file_storage::root(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t{} && index < end_file()); + if (m_files[index].root == nullptr) return sha256_hash(); + return sha256_hash(m_files[index].root); + } + + char const* file_storage::root_ptr(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t{} && index < end_file()); + return m_files[index].root; + } + + std::string file_storage::symlink(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t{} && index < end_file()); + aux::file_entry const& fe = m_files[index]; + if (fe.symlink_index == aux::file_entry::not_a_symlink) + return {}; + + TORRENT_ASSERT(fe.symlink_index < int(m_symlinks.size())); + + auto const& link = m_symlinks[fe.symlink_index]; + + std::string ret; + ret.reserve(m_name.size() + link.size() + 1); + ret.assign(m_name); + append_path(ret, link); + return ret; + } + + std::string const& file_storage::internal_symlink(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t{} && index < end_file()); + aux::file_entry const& fe = m_files[index]; + TORRENT_ASSERT(fe.symlink_index < int(m_symlinks.size())); + + return m_symlinks[fe.symlink_index]; + } + + std::time_t file_storage::mtime(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t{} && index < end_file()); + 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()); + aux::file_entry const& fe = m_files[index]; + + boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc; + + if (fe.path_index == aux::file_entry::path_is_absolute) + { + process_string_lowercase(crc, fe.filename()); + } + else if (fe.path_index == aux::file_entry::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()); + aux::file_entry const& fe = m_files[index]; + + std::string ret; + + if (fe.path_index == aux::file_entry::path_is_absolute) + { + ret = fe.filename().to_string(); + } + else if (fe.path_index == aux::file_entry::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()); + aux::file_entry const& fe = m_files[index]; + + if (fe.path_index != aux::file_entry::path_is_absolute + && fe.path_index != aux::file_entry::no_path) + { + 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()); + aux::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; + } + + int file_storage::file_num_pieces(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + TORRENT_ASSERT_PRECOND(m_piece_length > 0); + auto const& f = m_files[index]; + + // this function only works for v2 torrents, where files are guaranteed to + // be aligned to pieces + TORRENT_ASSERT(f.pad_file == false); + TORRENT_ASSERT((static_cast(f.offset) % m_piece_length) == 0); + return aux::numeric_cast( + (static_cast(f.size) + m_piece_length - 1) / m_piece_length); + } + + index_range file_storage::file_piece_range(file_index_t const file) const + { + return {piece_index_t::diff_type{0}, piece_index_t::diff_type{file_num_pieces(file)}}; + } + + int file_storage::file_num_blocks(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + TORRENT_ASSERT_PRECOND(m_piece_length > 0); + auto const& f = m_files[index]; + + // this function only works for v2 torrents, where files are guaranteed to + // be aligned to pieces + TORRENT_ASSERT(f.pad_file == false); + TORRENT_ASSERT((static_cast(f.offset) % m_piece_length) == 0); + return int((f.size + default_block_size - 1) / default_block_size); + } + + int file_storage::file_first_piece_node(file_index_t index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + TORRENT_ASSERT_PRECOND(m_piece_length > 0); + int const piece_layer_size = merkle_num_leafs(file_num_pieces(index)); + return merkle_num_nodes(piece_layer_size) - piece_layer_size; + } + + int file_storage::file_first_block_node(file_index_t index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + TORRENT_ASSERT_PRECOND(m_piece_length > 0); + int const leaf_layer_size = merkle_num_leafs(file_num_blocks(index)); + return merkle_num_nodes(leaf_layer_size) - leaf_layer_size; + } + + file_flags_t file_storage::file_flags(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + aux::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()); + aux::file_entry const& fe = m_files[index]; + return fe.path_index == aux::file_entry::path_is_absolute; + } + +#if TORRENT_ABI_VERSION == 1 + sha1_hash file_storage::hash(aux::file_entry const& fe) const + { + int const index = int(&fe - &m_files.front()); + TORRENT_ASSERT_PRECOND(index >= file_index_t{} && index < end_file()); + if (index >= int(m_file_hashes.size())) return sha1_hash(nullptr); + return sha1_hash(m_file_hashes[index]); + } + + std::string file_storage::symlink(aux::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(aux::file_entry const& fe) const + { + int const index = int(&fe - &m_files.front()); + TORRENT_ASSERT_PRECOND(index >= file_index_t{} && index < end_file()); + if (index >= int(m_mtime.size())) return 0; + return m_mtime[index]; + } + + int file_storage::file_index(aux::file_entry const& fe) const + { + int const index = int(&fe - &m_files.front()); + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + return index; + } + + std::string file_storage::file_path(aux::file_entry const& fe + , std::string const& save_path) const + { + int const index = int(&fe - &m_files.front()); + TORRENT_ASSERT_PRECOND(index >= file_index_t{} && index < end_file()); + return file_path(index, save_path); + } + + std::string file_storage::file_name(aux::file_entry const& fe) const + { + return fe.filename().to_string(); + } + + std::int64_t file_storage::file_size(aux::file_entry const& fe) const + { + return fe.size; + } + + bool file_storage::pad_file_at(aux::file_entry const& fe) const + { + return fe.pad_file; + } + + std::int64_t file_storage::file_offset(aux::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::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); + swap(ti.m_v2, m_v2); + } + + void file_storage::canonicalize() + { + TORRENT_ASSERT(piece_length() >= 16 * 1024); + + // use this vector to track the new ordering of files + // this allows the use of STL algorthims despite them + // not supporting a custom swap functor + aux::vector new_order(end_file()); + for (auto i : file_range()) + new_order[i] = i; + + // remove any existing pad files + { + auto pad_begin = std::partition(new_order.begin(), new_order.end() + , [this](file_index_t i) { return !m_files[i].pad_file; }); + new_order.erase(pad_begin, new_order.end()); + } + + // TODO: this would be more efficient if m_paths was sorted first, such + // that a lower path index always meant sorted-before + + // sort files by path/name + std::sort(new_order.begin(), new_order.end() + , [this](file_index_t l, file_index_t r) + { + // assuming m_paths are unqiue! + auto const& lf = m_files[l]; + auto const& rf = m_files[r]; + if (lf.path_index != rf.path_index) + { + int const ret = path_compare(m_paths[lf.path_index], lf.filename() + , m_paths[rf.path_index], rf.filename()); + if (ret != 0) return ret < 0; + } + return lf.filename() < rf.filename(); + }); + + aux::vector new_files; + aux::vector new_file_hashes; + aux::vector new_mtime; + + // reserve enough space for the worst case after padding + new_files.reserve(new_order.size() * 2 - 1); + if (!m_file_hashes.empty()) + new_file_hashes.reserve(new_order.size() * 2 - 1); + if (!m_mtime.empty()) + new_mtime.reserve(new_order.size() * 2 - 1); + + // re-compute offsets and insert pad files as necessary + std::int64_t off = 0; + for (file_index_t i : new_order) + { + if ((off % piece_length()) != 0 && m_files[i].size > 0) + { + auto const pad_size = piece_length() - (off % piece_length()); + TORRENT_ASSERT(pad_size < piece_length()); + TORRENT_ASSERT(pad_size > 0); + new_files.emplace_back(); + auto& pad = new_files.back(); + pad.size = static_cast(pad_size); + pad.offset = static_cast(off); + off += pad_size; + pad.path_index = get_or_add_path(".pad"); + char name[30]; + std::snprintf(name, sizeof(name), "%" PRIu64, pad.size); + pad.set_name(name); + pad.pad_file = true; + + if (!m_file_hashes.empty()) + new_file_hashes.push_back(nullptr); + if (!m_mtime.empty()) + new_mtime.push_back(0); + } + + TORRENT_ASSERT(!m_files[i].pad_file); + new_files.emplace_back(std::move(m_files[i])); + + if (i < m_file_hashes.end_index()) + new_file_hashes.push_back(m_file_hashes[i]); + else if (!m_file_hashes.empty()) + new_file_hashes.push_back(nullptr); + + if (i < m_mtime.end_index()) + new_mtime.push_back(m_mtime[i]); + else if (!m_mtime.empty()) + new_mtime.push_back(0); + + auto& file = new_files.back(); + TORRENT_ASSERT(off < max_file_offset - static_cast(file.size)); + file.offset = static_cast(off); + off += file.size; + } + + m_files = std::move(new_files); + m_file_hashes = std::move(new_file_hashes); + m_mtime = std::move(new_mtime); + + m_total_size = off; + } + + 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; + } + + aux::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 < aux::file_entry::path_is_absolute) + { + 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) + { + aux::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 { + + bool files_compatible(file_storage const& lhs, file_storage const& rhs) + { + if (lhs.num_files() != rhs.num_files()) + return false; + + if (lhs.total_size() != rhs.total_size()) + return false; + + if (lhs.piece_length() != rhs.piece_length()) + return false; + + // for compatibility, only non-empty and non-pad files matter. + // those files all need to match in index, name, size and offset + for (file_index_t i : lhs.file_range()) + { + bool const lhs_relevant = !lhs.pad_file_at(i) && lhs.file_size(i) > 0; + bool const rhs_relevant = !rhs.pad_file_at(i) && rhs.file_size(i) > 0; + + if (lhs_relevant != rhs_relevant) + return false; + + if (!lhs_relevant) continue; + + // we deliberately ignore file attributes like "hidden", + // "executable" and mtime here. It's not critical they match + if (lhs.pad_file_at(i) != rhs.pad_file_at(i) + || lhs.file_size(i) != rhs.file_size(i) + || lhs.file_path(i) != rhs.file_path(i) + || lhs.file_offset(i) != rhs.file_offset(i)) + { + return false; + } + + if ((lhs.file_flags(i) & file_storage::flag_symlink) + && lhs.symlink(i) != rhs.symlink(i)) + { + return false; + } + } + return true; + } + + 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); + } + + int calc_num_pieces(file_storage const& fs) + { + return aux::numeric_cast( + (fs.total_size() + fs.piece_length() - 1) / fs.piece_length()); + } + + std::int64_t size_on_disk(file_storage const& fs) + { + std::int64_t ret = 0; + for (file_index_t i : fs.file_range()) + { + if (fs.pad_file_at(i)) continue; + ret += fs.file_size(i); + } + return ret; + } + + } // namespace aux +} diff --git a/src/file_view_pool.cpp b/src/file_view_pool.cpp new file mode 100644 index 0000000..ee59d24 --- /dev/null +++ b/src/file_view_pool.cpp @@ -0,0 +1,306 @@ +/* + +Copyright (c) 2016, 2019-2020, Arvid Norberg +Copyright (c) 2019, 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" + +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/file_view_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 + +using namespace libtorrent::flags; + +namespace libtorrent { namespace aux { + + file_view_pool::file_view_pool(int size) : m_size(size) {} + file_view_pool::~file_view_pool() = default; + + file_view file_view_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 +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + , std::shared_ptr open_unmap_lock +#endif + ) + { + // 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. + std::shared_ptr defer_destruction1; + std::shared_ptr defer_destruction2; + + std::unique_lock l(m_mutex); + + TORRENT_ASSERT(is_complete(p)); + auto& key_view = m_files.get<0>(); + auto i = key_view.find(file_id{st, file_index}); + + // make sure the write bit is set if we asked for it + // it's OK to use a read-write file if we just asked for read. But if + // we asked for write, the file we serve back must be opened in write + // mode + if (i != key_view.end() + && (!(m & open_mode::write) || (i->mode & open_mode::write))) + { + key_view.modify(i, [&](file_entry& e) + { + e.last_use = aux::time_now(); + }); + + auto& lru_view = m_files.get<1>(); + lru_view.relocate(m_files.project<1>(i), lru_view.begin()); + + return i->mapping->view(); + } + + if (int(m_files.size()) >= m_size - 1) + { + // the file cache is at its maximum size, close + // the least recently used file + defer_destruction1 = remove_oldest(l); + } + + l.unlock(); + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + std::unique_lock lou(*open_unmap_lock); +#endif + file_entry e({st, file_index}, fs.file_path(file_index, p), m + , fs.file_size(file_index) +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + , open_unmap_lock +#endif + ); +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + lou.unlock(); +#endif + l.lock(); + + // there's an edge case where two threads are racing to insert a newly + // opened file, one thread is opening a file for writing and the other + // fore reading. If the reading thread wins, it's important that the + // thread opening for writing still overwrites the file in the pool, + // since a file opened for reading and writing can be used for both. + // So, we can't move e in here, because we may need it again of the + // insertion failed. + // if the insertion failed, check to see if we can use the existing + // entry. If not, overwrite it with the newly opened file ``e``. + bool added; + std::tie(i, added) = key_view.insert(e); + if (added == false) + { + // this is the case where this file was already in the pool. Make + // sure we can use it. If we asked for write mode, it must have been + // opened in write mode too. + TORRENT_ASSERT(i != key_view.end()); + + if ((m & open_mode::write) && !(i->mode & open_mode::write)) + { + key_view.modify(i, [&](file_entry& fe) + { + defer_destruction2 = std::move(fe.mapping); + fe = std::move(e); + }); + } + + auto& lru_view = m_files.get<1>(); + lru_view.relocate(m_files.project<1>(i), lru_view.begin()); + } + + return i->mapping->view(); + } + + file_open_mode_t to_file_open_mode(open_mode_t const mode) + { + return ((mode & open_mode::write) + ? file_open_mode::read_write : file_open_mode::read_only) + | ((mode & open_mode::no_atime) + ? file_open_mode::no_atime : file_open_mode::read_only) + ; + } + + std::vector file_view_pool::get_status(storage_index_t const st) const + { + std::vector ret; + { + std::unique_lock l(m_mutex); + + auto& key_view = m_files.get<0>(); + auto const start = key_view.lower_bound(file_id{st, file_index_t(0)}); + auto const end = key_view.upper_bound(file_id{st, std::numeric_limits::max()}); + + for (auto i = start; i != end; ++i) + { + ret.push_back({i->key.second + , to_file_open_mode(i->mode) + , i->last_use}); + } + } + return ret; + } + + std::shared_ptr file_view_pool::remove_oldest(std::unique_lock&) + { + auto& lru_view = m_files.get<1>(); + if (lru_view.size() == 0) return {}; + + auto mapping = std::move(lru_view.back().mapping); + lru_view.pop_back(); + + // closing a file may be long running operation (mac os x) + // let the caller destruct it once it has released the mutex + return mapping; + } + + void file_view_pool::release(storage_index_t const st, file_index_t file_index) + { + std::unique_lock l(m_mutex); + + auto& key_view = m_files.get<0>(); + auto const i = key_view.find(file_id{st, file_index}); + if (i == key_view.end()) return; + + auto mapping = std::move(i->mapping); + key_view.erase(i); + + // closing a file may take a long time (mac os x), so make sure + // we're not holding the mutex + l.unlock(); + } + + // closes files belonging to the specified + // storage, or all if none is specified. + void file_view_pool::release() + { + std::unique_lock l(m_mutex); + std::unique_lock l2(m_destruction_mutex); + m_deferred_destruction = std::move(m_files); + l.unlock(); + + // the files and mappings will be destructed here, not holding the main + // mutex + m_deferred_destruction.clear(); + } + + void file_view_pool::release(storage_index_t const st) + { + std::vector> defer_destruction; + + std::unique_lock l(m_mutex); + + auto& key_view = m_files.get<0>(); + auto const begin = key_view.lower_bound(file_id{st, file_index_t(0)}); + auto const end = key_view.upper_bound(file_id{st, std::numeric_limits::max()}); + + for (auto it = begin; it != end; ++it) + defer_destruction.emplace_back(std::move(it->mapping)); + + if (begin != end) key_view.erase(begin, end); + l.unlock(); + // the files are closed here while the lock is not held + } + + void file_view_pool::resize(int const 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.emplace_back(remove_oldest(l)); + } + + void file_view_pool::close_oldest() + { + // closing a file may be long running operation (mac os x) + // destruct it after the mutex is released + std::shared_ptr deferred_destruction; + + std::unique_lock l(m_mutex); + deferred_destruction = remove_oldest(l); + } + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + void file_view_pool::flush_next_file() + { + std::shared_ptr mapping; + { + std::unique_lock l(m_mutex); + auto& flush_view = m_files.get<2>(); + if (flush_view.size() == 0) return; + + auto it = std::prev(flush_view.end()); + if (it->dirty_bytes == 0) return; + mapping = it->mapping; + flush_view.modify(it, [](file_entry& e) { e.dirty_bytes = 0; }); + } + + // we invoke flush after we release the mutex + mapping->flush(); + } + + void file_view_pool::record_file_write(storage_index_t const st + , file_index_t const file_index, uint64_t const bytes) + { + std::unique_lock l(m_mutex); + auto& key_view = m_files.get<0>(); + auto i = key_view.find(file_id{st, file_index}); + if (i == key_view.end()) return; + key_view.modify(i, [bytes](file_entry& e) { e.dirty_bytes += bytes; }); + } +#endif +} +} + +#endif // HAVE_MMAP || HAVE_MAP_VIEW_OF_FILE + diff --git a/src/fingerprint.cpp b/src/fingerprint.cpp new file mode 100644 index 0000000..15901ac --- /dev/null +++ b/src/fingerprint.cpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016-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 "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..fbeead6 --- /dev/null +++ b/src/generate_peer_id.cpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2018-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 "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..14735d1 --- /dev/null +++ b/src/gzip.cpp @@ -0,0 +1,265 @@ +/* + +Copyright (c) 2008, 2010, 2014-2019, Arvid Norberg +Copyright (c) 2017, 2019, 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/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/hash_picker.cpp b/src/hash_picker.cpp new file mode 100644 index 0000000..a98aaaa --- /dev/null +++ b/src/hash_picker.cpp @@ -0,0 +1,427 @@ +/* + +Copyright (c) 2017, BitTorrent Inc. +Copyright (c) 2019-2020, Steven Siloti +Copyright (c) 2019-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/hash_picker.hpp" +#include "libtorrent/aux_/merkle.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +namespace libtorrent +{ + namespace + { + time_duration const min_request_interval = seconds(3); + } + +/* +merkle tree for a file: + + ^ x +proof_layer | x x + ^ x [****************] + | x x x x x x x x + base | x x x x x x x x x x x x x x x x <- block hash layer, leaves + -------> + index + -----------------> + count +*/ + +bool validate_hash_request(hash_request const& hr, file_storage const& fs) +{ + // limit the size of the base layer to something reasonable + // Blocks are requested for an entire piece so this limit + // effectivly caps the piece size we can handle. A limit of 8192 + // corresponds to a piece size of 128MB. + + if (hr.file < file_index_t{0} + || hr.file >= fs.end_file() + || hr.base < 0 + || hr.index < 0 + || hr.count <= 0 + || hr.count > 8192 + || hr.proof_layers < 0) + return false; + + int const num_leafs = merkle_num_leafs(fs.file_num_blocks(hr.file)); + int const num_layers = merkle_num_layers(num_leafs); + + if (hr.base >= num_layers) return false; + + // the number of hashes at the specified level + int const level_size = num_leafs >> hr.base; + + // [index, index + count] must be within the number of nodes at the specified + // level + if (hr.index >= level_size || hr.index + hr.count > level_size) + return false; + + if (hr.proof_layers >= num_layers - hr.base) return false; + + return true; +} + + hash_picker::hash_picker(file_storage const& files + , aux::vector& trees) + : m_files(files) + , m_merkle_trees(trees) + , m_piece_layer(merkle_num_layers(files.piece_length() / default_block_size)) + , m_piece_tree_root_layer(m_piece_layer + merkle_num_layers(512)) + { + m_piece_hash_requested.resize(trees.size()); + for (file_index_t f(0); f != m_files.end_file(); ++f) + { + if (m_files.pad_file_at(f)) continue; + + auto const& tree = m_merkle_trees[f]; + auto const v = tree.verified_leafs(); + + if (m_files.file_size(f) <= m_files.piece_length()) + continue; + + m_piece_hash_requested[f].resize((m_files.file_num_pieces(f) + 511) / 512); + + int const piece_layer_idx = merkle_num_layers( + merkle_num_leafs(m_files.file_num_blocks(f))) - m_piece_layer; + int const piece_layer_start = merkle_layer_start(piece_layer_idx); + + // check for hashes we already have and flag entries in m_piece_hash_requested + // so that we don't request them again + for (int i = 0; i < int(m_piece_hash_requested[f].size()); ++i) + { + for (int j = i * 512;; ++j) + { + if (j == (i + 1) * 512 || j >= m_files.file_num_pieces(f)) + { + m_piece_hash_requested[f][i].have = true; + break; + } + if ((m_files.piece_length() == default_block_size && !v[std::size_t(j)]) + || (m_files.piece_length() > default_block_size + && !tree.has_node(piece_layer_start + j))) + break; + } + } + } + } + + hash_request hash_picker::pick_hashes(typed_bitfield const& pieces) + { + auto const now = aux::time_now(); + + // this is for a future per-block request feature +#if 0 + if (!m_priority_block_requests.empty()) + { + auto& req = m_priority_block_requests.front(); + node_index const nidx(req.file, m_files.file_first_block_node(req.file) + req.block); + hash_request hash_req(req.file + , 0 + , req.block + , 2 + , layers_to_verify(nidx) + 1); + req.num_requests++; + std::sort(m_priority_block_requests.begin(), m_priority_block_requests.end()); + return hash_req; + } +#endif + + if (!m_piece_block_requests.empty()) + { + auto const req = std::find_if(m_piece_block_requests.begin(), m_piece_block_requests.end() + , [now](piece_block_request const& e) + { return e.last_request == min_time() || e.last_request - now > min_request_interval; }); + if (req != m_piece_block_requests.end()) + { + int const blocks_per_piece = m_files.piece_length() / default_block_size; + + // number of blocks from the start of the file + int const first_block = static_cast(req->piece) * blocks_per_piece; + node_index const nidx(req->file, m_files.file_first_block_node(req->file) + first_block); + hash_request hash_req(req->file + , 0 + , first_block + , blocks_per_piece + , layers_to_verify(nidx) + merkle_num_layers(blocks_per_piece)); + req->num_requests++; + req->last_request = now; + std::sort(m_piece_block_requests.begin(), m_piece_block_requests.end()); + return hash_req; + } + } + + for (file_index_t fidx(0); fidx < m_piece_hash_requested.end_index(); ++fidx) + { + if (m_files.pad_file_at(fidx)) continue; + + int const file_first_piece = int(m_files.file_offset(fidx) / m_files.piece_length()); + int const num_layers = file_num_layers(fidx); + int const piece_tree_root_layer = std::max(0, num_layers - m_piece_tree_root_layer); + int const piece_tree_root_start = merkle_layer_start(piece_tree_root_layer); + + int i = -1; + for (auto& r : m_piece_hash_requested[fidx]) + { + ++i; + if (r.have || + (r.last_request != min_time() + && now - r.last_request < min_request_interval)) + { + continue; + } + + bool have = false; + for (int p = i * 512; p < std::min((i + 1) * 512, m_files.file_num_pieces(fidx)); ++p) + { + if (pieces[piece_index_t{file_first_piece + p}]) + { + have = true; + break; + } + } + + if (!have) continue; + + int const piece_tree_root = piece_tree_root_start + i; + + ++r.num_requests; + r.last_request = now; + + int const piece_tree_num_layers + = num_layers - piece_tree_root_layer - m_piece_layer; + + return hash_request(fidx + , m_piece_layer + , i * 512 + , std::min(512, merkle_num_leafs(int(m_files.file_num_pieces(fidx) - i * 512))) + , layers_to_verify({ fidx, piece_tree_root }) + piece_tree_num_layers); + } + } + + return {}; + } + + add_hashes_result hash_picker::add_hashes(hash_request const& req, span hashes) + { + TORRENT_ASSERT(validate_hash_request(req, m_files)); + + int const unpadded_count = std::min(req.count, m_files.file_num_pieces(req.file) - req.index); + int const leaf_count = merkle_num_leafs(req.count); + int const base_num_layers = merkle_num_layers(leaf_count); + int const num_uncle_hashes = std::max(0, req.proof_layers - base_num_layers + 1); + + if (req.count + num_uncle_hashes != hashes.size()) + return add_hashes_result(false); + + // for now we rely on only requesting piece hashes in 512 chunks + if (req.base == m_piece_layer + && req.count != 512 + && (req.count > 512 || unpadded_count != m_files.file_num_pieces(req.file) - req.index)) + return add_hashes_result(false); + + // for now we only support receiving hashes at the piece and leaf layers + if (req.base != m_piece_layer && req.base != 0) + return add_hashes_result(false); + + // the incoming list of hashes is really two separate lists, the lowest + // layer of hashes we requested (typically the block- or piece layer). + // There are req.count of those, then there are the uncle hashes + // required to prove the correctness of the first hashes, to anchor the + // new hashes in the existing tree + auto const uncle_hashes = hashes.subspan(req.count); + hashes = hashes.first(req.count); + + TORRENT_ASSERT(uncle_hashes.size() == num_uncle_hashes); + + int const base_layer_idx = file_num_layers(req.file) - req.base; + + if (base_layer_idx <= 0) + return add_hashes_result(false); + + add_hashes_result ret(true); + + auto& dst_tree = m_merkle_trees[req.file]; + int const dest_start_idx = merkle_to_flat_index(base_layer_idx, req.index); + auto const file_piece_offset = m_files.piece_index_at_file(req.file) - piece_index_t{0}; + auto results = dst_tree.add_hashes(dest_start_idx, file_piece_offset, hashes, uncle_hashes); + + if (!results) + return add_hashes_result(false); + + ret.hash_failed = std::move(results->failed); + ret.hash_passed = std::move(results->passed); + + return ret; + } + + set_block_hash_result hash_picker::set_block_hash(piece_index_t const piece + , int const offset, sha256_hash const& h) + { + TORRENT_ASSERT(offset >= 0); + auto const f = m_files.file_index_at_piece(piece); + + if (m_files.pad_file_at(f)) + { + return { 0, 0 }; + } + + auto& merkle_tree = m_merkle_trees[f]; + piece_index_t const file_first_piece = m_files.piece_index_at_file(f); + std::int64_t const block_offset = static_cast(piece) * std::int64_t(m_files.piece_length()) + + offset - m_files.file_offset(f); + int const block_index = aux::numeric_cast(block_offset / default_block_size); + + if (h.is_all_zeros()) + { + TORRENT_ASSERT_FAIL(); + return set_block_hash_result::block_hash_failed(); + } + + // TODO: use structured bindings in C++17 + aux::merkle_tree::set_block_result result; + int leafs_index; + int leafs_size; + std::tie(result, leafs_index, leafs_size) = merkle_tree.set_block(block_index, h); + + if (result == aux::merkle_tree::set_block_result::unknown) + return set_block_hash_result::unknown(); + if (result == aux::merkle_tree::set_block_result::hash_failed) + return set_block_hash_result::piece_hash_failed(); + if (result == aux::merkle_tree::set_block_result::block_hash_failed) + return set_block_hash_result::block_hash_failed(); + + int const blocks_per_piece = m_files.piece_length() / default_block_size; + return { int(leafs_index - static_cast(piece - file_first_piece) * blocks_per_piece) + , std::min(leafs_size, m_files.file_num_pieces(f) * blocks_per_piece - leafs_index) }; + } + + void hash_picker::hashes_rejected(hash_request const& req) + { + TORRENT_ASSERT(req.base == m_piece_layer && req.index % 512 == 0); + + for (int i = req.index; i < req.index + req.count; i += 512) + { + m_piece_hash_requested[req.file][i / 512].last_request = min_time(); + --m_piece_hash_requested[req.file][i / 512].num_requests; + } + + // this is for a future per-block request feature +#if 0 + else if (req.base == 0) + { + priority_block_request block_req(req.file, req.index); + auto const existing_req = std::find( + m_priority_block_requests.begin() + , m_priority_block_requests.end() + , block_req); + + if (existing_req == m_priority_block_requests.end()) + { + m_priority_block_requests.insert(m_priority_block_requests.begin() + , priority_block_request(req.file, req.index)); + } + } +#endif + } + + void hash_picker::verify_block_hashes(piece_index_t const index) + { + file_index_t const fidx = m_files.file_index_at_piece(index); + piece_index_t::diff_type const piece = index - m_files.piece_index_at_file(fidx); + piece_block_request req(fidx, piece); + + if (std::find(m_piece_block_requests.begin(), m_piece_block_requests.end(), req) + != m_piece_block_requests.end()) + return; // already requested + + m_piece_block_requests.insert(m_piece_block_requests.begin(), req); + } + + bool hash_picker::have_hash(piece_index_t const index) const + { + file_index_t const f = m_files.file_index_at_piece(index); + if (m_files.file_size(f) <= m_files.piece_length()) return true; + piece_index_t const file_first_piece(int(m_files.file_offset(f) / m_files.piece_length())); + return m_merkle_trees[f].has_node(m_files.file_first_piece_node(f) + int(index - file_first_piece)); + } + + bool hash_picker::have_all(file_index_t const file) const + { + return m_merkle_trees[file].is_complete(); + } + + bool hash_picker::have_all() const + { + for (file_index_t f : m_files.file_range()) + if (!have_all(f)) return false; + return true; + } + + bool hash_picker::piece_verified(piece_index_t const piece) const + { + file_index_t const f = m_files.file_index_at_piece(piece); + piece_index_t const file_first_piece(int(m_files.file_offset(f) / m_files.piece_length())); + int const block_offset = static_cast(piece - file_first_piece) * (m_files.piece_length() / default_block_size); + int const blocks_in_piece = m_files.blocks_in_piece2(piece); + return m_merkle_trees[f].blocks_verified(block_offset, blocks_in_piece); + } + + int hash_picker::layers_to_verify(node_index idx) const + { + // the root layer doesn't have a sibling so it should never + // be requested as a proof layer + // return -1 to signal to the caller that no proof is required + // even for the first layer it is trying to verify + if (idx.node == 0) return -1; + + int layers = 0; + int const file_internal_layers = merkle_num_layers(merkle_num_leafs(m_files.file_num_pieces(idx.file))) - 1; + auto const& tree = m_merkle_trees[idx.file]; + + for (;;) + { + idx.node = merkle_get_parent(idx.node); + if (tree.has_node(idx.node)) break; + layers++; + if (layers == file_internal_layers) return layers; + } + + return layers; + } + + int hash_picker::file_num_layers(file_index_t const idx) const + { + return merkle_num_layers(merkle_num_leafs(m_files.file_num_blocks(idx))); + } +} diff --git a/src/hasher.cpp b/src/hasher.cpp new file mode 100644 index 0000000..d390517 --- /dev/null +++ b/src/hasher.cpp @@ -0,0 +1,283 @@ +/* + +Copyright (c) 2013-2014, 2016-2020, Arvid Norberg +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017, 2019, Andrei Kurushin +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_/disable_deprecation_warnings_push.hpp" + +namespace libtorrent { + +TORRENT_CRYPTO_NAMESPACE + + 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_CNG +#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_CNG + m_context.update(data); +#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_CNG + m_context.get_hash(digest.data(), digest.size()); +#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_CNG + m_context.reset(); +#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 + } + + hasher256::hasher256() + { +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_open(&m_context, GCRY_MD_SHA256, 0); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA256_Init(&m_context); +#elif TORRENT_USE_CNG +#elif TORRENT_USE_CRYPTOAPI_SHA_512 +#elif defined TORRENT_USE_LIBCRYPTO + SHA256_Init(&m_context); +#else + SHA256_init(m_context); +#endif + } + + hasher256::hasher256(span data) + : hasher256() + { + update(data); + } + + hasher256::hasher256(char const* data, int len) + : hasher256() + { + TORRENT_ASSERT(len > 0); + update({ data, len }); + } + +#ifdef TORRENT_USE_LIBGCRYPT + hasher256::hasher256(hasher256 const& h) + { + gcry_md_copy(&m_context, h.m_context); + } + + hasher256& hasher256::operator=(hasher256 const& h) & + { + if (this == &h) return *this; + gcry_md_close(m_context); + gcry_md_copy(&m_context, h.m_context); + return *this; + } +#else + hasher256::hasher256(hasher256 const&) = default; + hasher256& hasher256::operator=(hasher256 const&) & = default; +#endif + + hasher256& hasher256::update(char const* data, int len) + { + return update({ data, len }); + } + + hasher256& hasher256::update(span data) + { + TORRENT_ASSERT(!data.empty()); +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_write(m_context, data.data(), data.size()); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA256_Update(&m_context, reinterpret_cast(data.data()), CC_LONG(data.size())); +#elif TORRENT_USE_CNG + m_context.update(data); +#elif TORRENT_USE_CRYPTOAPI_SHA_512 + m_context.update(data); +#elif defined TORRENT_USE_LIBCRYPTO + SHA256_Update(&m_context, reinterpret_cast(data.data()) + , static_cast(data.size())); +#else + SHA256_update(m_context, reinterpret_cast(data.data()) + , static_cast(data.size())); +#endif + return *this; + } + + sha256_hash hasher256::final() + { + sha256_hash digest; +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_final(m_context); + digest.assign((char const*)gcry_md_read(m_context, 0)); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA256_Final(reinterpret_cast(digest.data()), &m_context); +#elif TORRENT_USE_CNG + m_context.get_hash(digest.data(), digest.size()); +#elif TORRENT_USE_CRYPTOAPI_SHA_512 + m_context.get_hash(digest.data(), digest.size()); +#elif defined TORRENT_USE_LIBCRYPTO + SHA256_Final(reinterpret_cast(digest.data()), &m_context); +#else + SHA256_final(reinterpret_cast(digest.data()), m_context); +#endif + return digest; + } + + void hasher256::reset() + { +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_reset(m_context); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA256_Init(&m_context); +#elif TORRENT_USE_CNG + m_context.reset(); +#elif TORRENT_USE_CRYPTOAPI_SHA_512 + m_context.reset(); +#elif defined TORRENT_USE_LIBCRYPTO + SHA256_Init(&m_context); +#else + SHA256_init(m_context); +#endif + } + + hasher256::~hasher256() + { +#if defined TORRENT_USE_LIBGCRYPT + gcry_md_close(m_context); +#endif + } + +TORRENT_CRYPTO_NAMESPACE_END + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +} diff --git a/src/hex.cpp b/src/hex.cpp new file mode 100644 index 0000000..8250817 --- /dev/null +++ b/src/hex.cpp @@ -0,0 +1,106 @@ +/* + +Copyright (c) 2015-2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2016, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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..fea55ba --- /dev/null +++ b/src/http_connection.cpp @@ -0,0 +1,897 @@ +/* + +Copyright (c) 2007-2020, Arvid Norberg +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2017, Jan Berkel +Copyright (c) 2017, Steven Siloti +Copyright (c) 2019, patch3proxyheaders915360 +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/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 "libtorrent/io_context.hpp" +#include "libtorrent/i2p_stream.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" +#include "libtorrent/ssl.hpp" + +#include +#include +#include +#include + +using namespace std::placeholders; + +namespace libtorrent { + +http_connection::http_connection(io_context& ios + , aux::resolver_interface& resolver + , http_handler handler + , bool bottled + , int max_bottled_buffer_size + , http_connect_handler ch + , http_filter_handler fh + , hostname_filter_handler hfh +#if TORRENT_USE_SSL + , ssl::context* ssl_ctx +#endif + ) + : m_ios(ios) + , m_next_ep(0) +#if TORRENT_USE_SSL + , m_ssl_ctx(ssl_ctx) +#endif +#if TORRENT_USE_I2P + , m_i2p_conn(nullptr) +#endif + , m_resolver(resolver) + , m_handler(std::move(handler)) + , m_connect_handler(std::move(ch)) + , m_filter_handler(std::move(fh)) + , m_hostname_filter_handler(std::move(hfh)) + , 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, aux::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) + { + post(m_ios, std::bind(&http_connection::callback + , me, ec, span{})); + return; + } + + if (m_hostname_filter_handler && !m_hostname_filter_handler(*this, hostname)) + { + error_code err(errors::blocked_by_idna); + post(m_ios, std::bind(&http_connection::callback + , me, err, span{})); + return; + } + + if (protocol != "http" +#if TORRENT_USE_SSL + && protocol != "https" +#endif + ) + { + error_code err(errors::unsupported_url_protocol); + post(m_ios, 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 + , aux::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; + m_timer.expires_after(m_completion_timeout); + 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; + +#if TORRENT_USE_SSL + TORRENT_ASSERT(!ssl || m_ssl_ctx != nullptr); +#endif + + if (m_sock && 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 && 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) + { + post(m_ios, 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; +#if TORRENT_USE_SSL + 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. + m_sock.emplace(instantiate_connection(m_ios + , proxy ? *proxy : null_proxy, userdata, nullptr, false, false)); + + if (m_bind_addr) + { + error_code ec; + 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) + { + post(m_ios, std::bind(&http_connection::callback + , me, ec, span{})); + return; + } + } + + error_code ec; + setup_ssl_hostname(*m_sock, hostname, ec); + if (ec) + { + post(m_ios, 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"); + c->m_timer.expires_at(c->m_start_time + c->m_completion_timeout); + c->m_timer.async_wait(std::bind(&http_connection::on_timeout, p, _1)); +} + +void http_connection::close(bool force) +{ + if (m_abort) return; + + if (m_sock) + { + error_code ec; + if (force) + { + m_sock->close(ec); + m_timer.cancel(); + } + else + { + async_shutdown(*m_sock, shared_from_this()); + } + } + else + m_timer.cancel(); + + m_limiter_timer.cancel(); + + 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(boost::get(m_sock.get_ptr())); +#if TORRENT_USE_SSL + TORRENT_ASSERT(m_ssl == false); +#endif + boost::get(*m_sock).set_destination(destination); + boost::get(*m_sock).set_command(i2p_stream::cmd_connect); + boost::get(*m_sock).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 aux::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 +#if TORRENT_USE_SSL + if (m_ssl) + { + TORRENT_ASSERT(boost::get>(m_sock.get_ptr())); + boost::get>(*m_sock).next_layer().set_dst_name(m_hostname); + } + else +#endif + { + TORRENT_ASSERT(boost::get(m_sock.get_ptr())); + boost::get(*m_sock).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; + m_timer.cancel(); + 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()) + { + m_timer.cancel(); + 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)); + + m_limiter_timer_active = true; + m_limiter_timer.expires_after(milliseconds(250)); + 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) + { + m_limiter_timer_active = true; + m_limiter_timer.expires_after(milliseconds(250)); + 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..d3d725a --- /dev/null +++ b/src/http_parser.cpp @@ -0,0 +1,648 @@ +/* + +Copyright (c) 2008-2019, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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; + // at least GCC-5.4 for ARM (on travis) has a libstdc++ whose debug map$ + // doesn't seem to support transparent comparators$ +#if ! defined _GLIBCXX_DEBUG + auto const i = m_header.find(key); +#else + auto const i = m_header.find(std::string(key)); +#endif + if (i == m_header.end()) return empty; + return i->second; + } + + boost::optional http_parser::header_duration(string_view const key) const + { + // at least GCC-5.4 for ARM (on travis) has a libstdc++ whose debug map$ + // doesn't seem to support transparent comparators$ +#if ! defined _GLIBCXX_DEBUG + auto const i = m_header.find(key); +#else + auto const i = m_header.find(std::string(key)); +#endif + 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 + { + if (m_state != read_body) return {}; + 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; + if (chunk_end - offset > buffer.size() + || (i.second - i.first) >= std::numeric_limits::max()) + { + // invalid chunk header. Return the body we've parsed out so far + return buffer.first(write_ptr - buffer.data()); + } + 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..2d3fee7 --- /dev/null +++ b/src/http_seed_connection.cpp @@ -0,0 +1,487 @@ +/* + +Copyright (c) 2008-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016, 2018, Steven Siloti +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2018, TheOriginalWinCat +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/invariant_check.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/hex.hpp" // for is_hex +#include "libtorrent/random.hpp" +#include "libtorrent/optional.hpp" + +namespace libtorrent { + + http_seed_connection::http_seed_connection(peer_connection_args& 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() + { + peer_id pid; + aux::random_bytes(pid); + set_pid(pid); + + // 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({associated_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; + } + + // when SSRF mitigation is enabled, a web seed on the internet (is_global()) + // is not allowed to redirect to a server on the local network, so we set + // the no_local_ips flag + auto const web_seed_flags = torrent::ephemeral + | ((m_settings.get_bool(settings_pack::ssrf_mitigation) && aux::is_global(remote().address())) + ? torrent::no_local_ips : web_seed_flag_t{}); + + // add the redirected url and remove the current one + t->add_web_seed(location, web_seed_entry::http_seed + , std::string{}, web_seed_entry::headers_t{} + , web_seed_flags); + 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 = server_version; + else + m_server_string = m_host; + + 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; + // TODO: technically, this isn't supposed to happen, but it seems to + // sometimes. Some of the accounting is probably wrong in certain + // cases + if (payload > outstanding_bytes()) payload = outstanding_bytes(); + 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_tracker_connection.cpp b/src/http_tracker_connection.cpp new file mode 100644 index 0000000..03481a3 --- /dev/null +++ b/src/http_tracker_connection.cpp @@ -0,0 +1,676 @@ +/* + +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2004-2020, Arvid Norberg +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2016-2018, Steven Siloti +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/resolver_interface.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/aux_/array.hpp" + +namespace libtorrent { + + http_tracker_connection::http_tracker_connection( + io_context& ios + , tracker_manager& man + , tracker_request req + , std::weak_ptr c) + : tracker_connection(man, std::move(req), ios, std::move(c)) + , m_ioc(ios) + {} + + void http_tracker_connection::start() + { + std::string url = tracker_req().url; + + if (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) + { + fail(errors::scrape_not_available, operation_t::bittorrent); + 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 + auto const arguments_start = url.find('?'); + if (arguments_start != std::string::npos) + { + // tracker URLs that come pre-baked with query string arguments will be + // rejected when SSRF-mitigation is enabled + bool const ssrf_mitigation = settings.get_bool(settings_pack::ssrf_mitigation); + if (ssrf_mitigation && has_tracker_query_string(string_view(url).substr(arguments_start + 1))) + { + fail(errors::ssrf_mitigation, operation_t::bittorrent); + return; + } + url += "&"; + } + else + { + url += "?"; + } + + url += "info_hash="; + url += escape_string({tracker_req().info_hash.data(), 20}); + + if (!(tracker_req().kind & tracker_request::scrape_request)) + { + static aux::array const 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 != event_t::none) ? "&event=" : "" + , (tracker_req().event != event_t::none) ? event_string[static_cast(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, operation_t::bittorrent + , "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) + { + std::string const ip = v4.to_string(); + url += "&ipv4="; + url += escape_string(ip); + } + } + if (!tracker_req().ipv6.empty() && !i2p) + { + for (auto const& v6 : tracker_req().ipv6) + { + std::string const ip = v6.to_string(); + url += "&ipv6="; + url += escape_string(ip); + } + } + + if (!tracker_req().outgoing_socket) + { + fail(errors::invalid_listen_socket, operation_t::get_interface + , "outgoing socket was closed"); + return; + } + + using namespace std::placeholders; + m_tracker_connection = std::make_shared(m_ioc, 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) + , std::bind(&http_tracker_connection::on_filter_hostname, shared_from_this(), _1, _2) +#if TORRENT_USE_SSL + , tracker_req().ssl_ctx +#endif + ); + + int const timeout = tracker_req().event == event_t::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 + bool const anon_user = settings.get_bool(settings_pack::anonymous_mode) + && !tracker_req().private_torrent; + std::string const user_agent = anon_user + ? "curl/7.81.0" + : 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 == event_t::stopped ? 2 : 1 + , ps.proxy_tracker_connections ? &ps : nullptr + , 5, user_agent, bind_interface() + , (tracker_req().event == event_t::stopped + ? aux::resolver_interface::cache_only : aux::resolver_flags{}) + | aux::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(lt::errors::announce_skipped, operation_t::get_interface); + return; + } + + aux::session_settings const& settings = m_man.settings(); + bool const ssrf_mitigation = settings.get_bool(settings_pack::ssrf_mitigation); + if (ssrf_mitigation && std::find_if(endpoints.begin(), endpoints.end() + , [](tcp::endpoint const& ep) { return ep.address().is_loopback(); }) != endpoints.end()) + { + // there is at least one loopback address in here. If the request + // path for this tracker is not /announce. filter all loopback + // addresses. + std::string path; + + error_code ec; + std::tie(std::ignore, std::ignore, std::ignore, std::ignore, path) + = parse_url_components(c.url(), ec); + if (ec) + { + fail(ec, operation_t::parse_address); + return; + } + + // this is mitigation for Server Side request forgery. Any tracker + // announce to localhost need to look like a standard BitTorrent + // announce + if (path.substr(0, 9) != "/announce") + { + for (auto i = endpoints.begin(); i != endpoints.end();) + { + if (i->address().is_loopback()) + i = endpoints.erase(i); + else + ++i; + } + } + + if (endpoints.empty()) + { + fail(errors::ssrf_mitigation, operation_t::bittorrent); + 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, operation_t::bittorrent); + } + + // returns true if the hostname is allowed + bool http_tracker_connection::on_filter_hostname(http_connection& + , string_view hostname) + { + aux::session_settings const& settings = m_man.settings(); + if (settings.get_bool(settings_pack::allow_idna)) return true; + return !is_idna(hostname); + } + + 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, operation_t::sock_read); + return; + } + + if (!parser.header_finished()) + { + fail(boost::asio::error::eof, operation_t::sock_read); + return; + } + + if (parser.status_code() != 200) + { + fail(error_code(parser.status_code(), http_category()) + , operation_t::bittorrent + , 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, operation_t::bittorrent + , resp.failure_reason.c_str() + , resp.interval, resp.min_interval); + close(); + return; + } + + // do slightly different things for scrape requests + if (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 + , tracker_request_flags_t 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 (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 (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 = aux::read_v4_address(peers).to_bytes(); + p.port = aux::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 = aux::read_v6_address(peers).to_bytes(); + p.port = aux::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 != event_t::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 = aux::read_v4_address(p); + else if (ip_ent.string_length() == std::tuple_size::value) + resp.external_ip = aux::read_v6_address(p); + } + + return resp; + } +} diff --git a/src/i2p_stream.cpp b/src/i2p_stream.cpp new file mode 100644 index 0000000..e52fb06 --- /dev/null +++ b/src/i2p_stream.cpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2009, 2011, 2013-2020, Arvid Norberg +Copyright (c) 2016, 2019, Alden Torres +Copyright (c) 2017, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 + +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_context& 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; + } + + i2p_stream::i2p_stream(io_context& io_context) + : proxy_base(io_context) + , 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 + + static_assert(std::is_nothrow_move_constructible::value + , "should be nothrow move constructible"); +} + +#endif diff --git a/src/identify_client.cpp b/src/identify_client.cpp new file mode 100644 index 0000000..f873957 --- /dev/null +++ b/src/identify_client.cpp @@ -0,0 +1,440 @@ +/* + +Copyright (c) 2003-2010, 2012-2019, Arvid Norberg +Copyright (c) 2004, spyhole +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2017, Alden Torres +Copyright (c) 2017, Col-blimp +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2019, gubatron +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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"} + , {"BI", "BiglyBT"} + , {"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"} + , {"LR", "LibreTorrent"} + , {"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"} + , {"ZO", "Zona"} + , {"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, "-SP", "BitSpirit 3.6"} + , {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..3ecd8b4 --- /dev/null +++ b/src/instantiate_connection.cpp @@ -0,0 +1,154 @@ +/* + +Copyright (c) 2007, 2010-2011, 2014-2020, Arvid Norberg +Copyright (c) 2018, Alden Torres +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/utp_socket_manager.hpp" +#include "libtorrent/aux_/instantiate_connection.hpp" +#include "libtorrent/aux_/utp_stream.hpp" +#include "libtorrent/ssl_stream.hpp" + +namespace libtorrent { namespace aux { + + // TODO: 2 peer_connection and tracker_connection should probably be flags + aux::socket_type instantiate_connection(io_context& ios + , aux::proxy_settings const& ps + , void* ssl_context + , utp_socket_manager* sm + , bool peer_connection + , bool tracker_connection) + { +#if !TORRENT_USE_SSL + TORRENT_UNUSED(ssl_context); +#endif + + if (sm) + { +#if TORRENT_USE_SSL + if (ssl_context) + { + ssl_stream s(ios, *static_cast(ssl_context)); + s.next_layer().set_impl(sm->new_utp_socket(&s.next_layer())); + return socket_type(std::move(s)); + } + else +#endif + { + utp_stream s(ios); + s.set_impl(sm->new_utp_socket(&s)); + return socket_type(std::move(s)); + } + } +#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); + i2p_stream s(ios); + s.set_proxy(ps.hostname, ps.port); + return socket_type(std::move(s)); + } +#endif + else if (ps.type == settings_pack::none + || (peer_connection && !ps.proxy_peer_connections) + || (tracker_connection && !ps.proxy_tracker_connections)) + { +#if TORRENT_USE_SSL + if (ssl_context) + { + return socket_type(ssl_stream(ios, *static_cast(ssl_context))); + } + else +#endif + { + return socket_type(tcp::socket(ios)); + } + } + else if (ps.type == settings_pack::http + || ps.type == settings_pack::http_pw) + { +#if TORRENT_USE_SSL + if (ssl_context) + { + ssl_stream s(ios, *static_cast(ssl_context)); + http_stream* str = &s.next_layer(); + str->set_proxy(ps.hostname, ps.port); + if (ps.type == settings_pack::http_pw) + str->set_username(ps.username, ps.password); + return socket_type(std::move(s)); + } + else +#endif + { + http_stream s(ios); + s.set_proxy(ps.hostname, ps.port); + if (ps.type == settings_pack::http_pw) + s.set_username(ps.username, ps.password); + return socket_type(std::move(s)); + } + + } + else if (ps.type == settings_pack::socks5 + || ps.type == settings_pack::socks5_pw + || ps.type == settings_pack::socks4) + { +#if TORRENT_USE_SSL + if (ssl_context) + { + ssl_stream s(ios, *static_cast(ssl_context)); + socks5_stream* str = &s.next_layer(); + 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); + return socket_type(std::move(s)); + } + else +#endif + { + socks5_stream s(ios); + s.set_proxy(ps.hostname, ps.port); + if (ps.type == settings_pack::socks5_pw) + s.set_username(ps.username, ps.password); + if (ps.type == settings_pack::socks4) + s.set_version(4); + return socket_type(std::move(s)); + } + } + TORRENT_ASSERT_FAIL(); + throw std::runtime_error("unknown socket type"); + } + +}} diff --git a/src/ip_filter.cpp b/src/ip_filter.cpp new file mode 100644 index 0000000..079cd04 --- /dev/null +++ b/src/ip_filter.cpp @@ -0,0 +1,285 @@ +/* + +Copyright (c) 2005-2007, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2017-2018, 2020, 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 // for next + +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { + + ip_filter::ip_filter() = default; + ip_filter::ip_filter(ip_filter const&) = default; + ip_filter::ip_filter(ip_filter&&) = default; + ip_filter& ip_filter::operator=(ip_filter const&) = default; + ip_filter& ip_filter::operator=(ip_filter&&) = default; + ip_filter::~ip_filter() = default; + + bool ip_filter::empty() const { return m_filter4.empty() && m_filter6.empty(); } + + 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()); + } + + port_filter::port_filter() = default; + port_filter::port_filter(port_filter const&) = default; + port_filter::port_filter(port_filter&&) = default; + port_filter& port_filter::operator=(port_filter const&) = default; + port_filter& port_filter::operator=(port_filter&&) = default; + port_filter::~port_filter() = default; + + 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); + } + +namespace aux { + + template + Addr zero() + { + Addr zero; + std::fill(zero.begin(), zero.end(), static_cast(0)); + return zero; + } + + 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; + } + + 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; + } + + template + Addr max_addr() + { + Addr tmp; + std::fill(tmp.begin(), tmp.end() + , (std::numeric_limits::max)()); + return tmp; + } + +#ifdef _MSC_VER +#define EXPORT_INST TORRENT_EXTRA_EXPORT +#else +#define EXPORT_INST +#endif + + template EXPORT_INST address_v4::bytes_type minus_one(address_v4::bytes_type const&); + template EXPORT_INST address_v6::bytes_type minus_one(address_v6::bytes_type const&); + template EXPORT_INST address_v4::bytes_type plus_one(address_v4::bytes_type const&); + template EXPORT_INST address_v6::bytes_type plus_one(address_v6::bytes_type const&); + template EXPORT_INST address_v4::bytes_type zero(); + template EXPORT_INST address_v6::bytes_type zero(); + template EXPORT_INST address_v4::bytes_type max_addr(); + template EXPORT_INST address_v6::bytes_type max_addr(); + + template + filter_impl::filter_impl() + { + // make the entire ip-range non-blocked + m_access_list.insert(range(zero(), 0)); + } + + template + bool filter_impl::empty() const + { + return m_access_list.empty() + || (m_access_list.size() == 1 && *m_access_list.begin() == range(zero(), 0)); + } + + template + void filter_impl::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()); + } + + template + std::uint32_t filter_impl::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 + template + std::vector> filter_impl::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; + } + + template class EXPORT_INST filter_impl; + template class EXPORT_INST filter_impl; + template class EXPORT_INST filter_impl; + + template EXPORT_INST std::vector> filter_impl::export_filter() const; + template EXPORT_INST std::vector> filter_impl::export_filter() const; + template EXPORT_INST std::vector> filter_impl::export_filter() const; + +#undef EXPORT_INST + +} // namespace aux +} diff --git a/src/ip_helpers.cpp b/src/ip_helpers.cpp new file mode 100644 index 0000000..f317e29 --- /dev/null +++ b/src/ip_helpers.cpp @@ -0,0 +1,124 @@ +/* + +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/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" + +namespace libtorrent { +namespace aux { + + 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(); + std::uint32_t ip = a4.to_uint(); + return (ip & 0xffff0000) == 0xa9fe0000; // 169.254.x.x + } + + bool is_local(address const& a) + { + TORRENT_TRY { + 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(); + std::uint32_t const ip = a4.to_uint(); + 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 + } TORRENT_CATCH(std::exception const&) { return false; } + } + + bool is_teredo(address const& addr) + { + TORRENT_TRY { + 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; + } TORRENT_CATCH(std::exception const&) { return false; } + } + + address ensure_v6(address const& a) + { + return a == address_v4() ? address_v6() : a; + } + +} +} + diff --git a/src/ip_notifier.cpp b/src/ip_notifier.cpp new file mode 100644 index 0000000..13d0eec --- /dev/null +++ b/src/ip_notifier.cpp @@ -0,0 +1,449 @@ +/* + +Copyright (c) 2016, 2020, Steven Siloti +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2017, Tim Niederhausen +Copyright (c) 2020, Tiger Wang +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 +#ifdef TORRENT_WINRT +#include +#endif +#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_context& ios) + : m_ios(ios) {} + + void async_wait(std::function cb) override + { + post(m_ios, [cb]() + { cb(make_error_code(boost::system::errc::not_supported)); }); + } + + void cancel() override {} + +private: + io_context& m_ios; +}; +#elif TORRENT_USE_NETLINK +struct ip_change_notifier_impl final : ip_change_notifier +{ + explicit ip_change_notifier_impl(io_context& 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_context& 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); + post(obj->m_ios, [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 + post(m_ios, [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_context& 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_context& 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); + post(obj->m_ios, [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 + post(m_ios, [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_context& 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_context& 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); + } + post(impl->m_ios, [c = std::move(cbs)]() + { + for (auto& cb : c) cb(error_code()); + }); + } + + io_context& 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_context& ios) + : m_ios(ios) {} + + void async_wait(std::function cb) override + { + post(m_ios, [cb]() + { cb(make_error_code(boost::system::errc::not_supported)); }); + } + + void cancel() override {} + +private: + io_context& m_ios; +}; +#endif + +} // anonymous namespace + + std::unique_ptr create_ip_notifier(io_context& ios) + { + return std::make_unique(ios); + } +}} diff --git a/src/ip_voter.cpp b/src/ip_voter.cpp new file mode 100644 index 0000000..7676c23 --- /dev/null +++ b/src/ip_voter.cpp @@ -0,0 +1,194 @@ +/* + +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016, 2018, 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/ip_voter.hpp" +#include "libtorrent/socket_io.hpp" // for hash_address +#include "libtorrent/random.hpp" // for random() +#include "libtorrent/aux_/time.hpp" // for aux::time_now() +#include "libtorrent/aux_/ip_helpers.hpp" // for is_local() etc. + +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 (ip.is_unspecified()) return false; + if (aux::is_local(ip)) return false; + if (ip.is_loopback()) 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, aux::ensure_v6(global6)}, {local4, aux::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[aux::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..93b25b7 --- /dev/null +++ b/src/kademlia/dht_settings.cpp @@ -0,0 +1,116 @@ +/* + +Copyright (c) 2017, 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 "libtorrent/config.hpp" +#include "libtorrent/kademlia/dht_settings.hpp" +#include "libtorrent/bdecode.hpp" + +namespace libtorrent { +namespace dht { + +#if TORRENT_ABI_VERSION <= 2 + 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; + } +#endif +} +} diff --git a/src/kademlia/dht_state.cpp b/src/kademlia/dht_state.cpp new file mode 100644 index 0000000..791dfe8 --- /dev/null +++ b/src/kademlia/dht_state.cpp @@ -0,0 +1,135 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017-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/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 = aux::read_v4_address(in); + else if (nid.string_length() == 36) + addr = aux::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); + aux::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 = aux::read_endpoint_list(nodes); + if (bdecode_node const nodes = e.dict_find_list("nodes6")) + ret.nodes6 = aux::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)); + aux::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..8a64820 --- /dev/null +++ b/src/kademlia/dht_storage.cpp @@ -0,0 +1,634 @@ +/* + +Copyright (c) 2015-2018, Alden Torres +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016, Steven Siloti +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2018, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/settings_pack.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include // for is_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(settings_interface 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.get_int(settings_pack::dht_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.get_int(settings_pack::dht_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(); + aux::write_endpoint(iter->addr, out); + str.resize(std::size_t(out - str.begin())); + + --to_pick; + } + } + + if (int(peersv.size()) < m_settings.get_int(settings_pack::dht_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.get_int(settings_pack::dht_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 = aux::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.get_int(settings_pack::dht_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.get_int(settings_pack::dht_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.get_int(settings_pack::dht_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.get_int(settings_pack::dht_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.get_int(settings_pack::dht_item_lifetime)) return; + + time_point const now = aux::time_now(); + time_duration lifetime = seconds(m_settings.get_int(settings_pack::dht_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: + settings_interface 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.get_int(settings_pack::dht_sample_infohashes_interval) + , 0, sample_infohashes_interval_max); + + int const max_count = aux::clamp(m_settings.get_int(settings_pack::dht_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( + settings_interface const& settings) +{ + return std::make_unique(settings); +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/dht_tracker.cpp b/src/kademlia/dht_tracker.cpp new file mode 100644 index 0000000..0d827f1 --- /dev/null +++ b/src/kademlia/dht_tracker.cpp @@ -0,0 +1,737 @@ +/* + +Copyright (c) 2006-2012, 2014-2020, Arvid Norberg +Copyright (c) 2014-2015, 2017, Steven Siloti +Copyright (c) 2015-2018, Alden Torres +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016, 2019, Andrei Kurushin +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2020, Fonic +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_v6 + +#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_context& ios + , send_fun_t send_fun + , aux::session_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(std::move(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.get_int(settings_pack::dht_upload_rate_limit)) + , m_last_tick(aux::time_now()) + , m_ioc(ios) + { + m_blocker.set_block_timer(m_settings.get_int(settings_pack::dht_block_timeout)); + m_blocker.set_rate_limit(m_settings.get_int(settings_pack::dht_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(m_ioc + , s, this, m_settings, nid, m_log, m_counters + , std::bind(&dht_tracker::get_node, this, _1, _2) + , m_storage)); + + update_storage_node_ids(); + +#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.to_string().c_str() + , aux::to_hex(n.first->second.dht.nid()).c_str()); + } +#endif + + if (m_running && n.second) + { + ADD_OUTSTANDING_ASYNC("dht_tracker::connection_timeout"); + n.first->second.connection_timer.expires_after(seconds(1)); + 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) + { +#ifndef TORRENT_DISABLE_LOGGING + if (m_log->should_log(dht_logger::tracker)) + { + address const local_address = s.get_local_endpoint().address(); + m_log->log(dht_logger::tracker, "removing DHT node on %s" + , local_address.to_string().c_str()); + } +#endif + m_nodes.erase(s); + + update_storage_node_ids(); + } + + void dht_tracker::start(find_data::nodes_callback const& f) + { + m_running = true; + + ADD_OUTSTANDING_ASYNC("dht_tracker::refresh_key"); + refresh_key({}); + + for (auto& n : m_nodes) + { + ADD_OUTSTANDING_ASYNC("dht_tracker::connection_timeout"); + n.second.connection_timer.expires_after(seconds(1)); + n.second.connection_timer.async_wait( + std::bind(&dht_tracker::connection_timeout, self(), n.first, _1)); + if (aux::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_after(seconds(5)); + m_refresh_timer.async_wait(std::bind(&dht_tracker::refresh_timeout, self(), _1)); + + m_state.clear(); + } + + void dht_tracker::stop() + { + m_running = false; + m_key_refresh_timer.cancel(); + for (auto& n : m_nodes) + n.second.connection_timer.cancel(); + m_refresh_timer.cancel(); + 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.active_requests.clear(); + s.dht_total_allocations = 0; + + for (auto& n : m_nodes) + n.second.dht.status(s); + } +#endif + + std::vector dht_tracker::dht_status() const + { + std::vector ret; + for (auto& n : m_nodes) + ret.emplace_back(n.second.dht.status()); + return ret; + } + + 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(); + deadline_timer& timer = n.connection_timer; + timer.expires_after(d); + 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.get_int(settings_pack::dht_block_timeout)); + m_blocker.set_rate_limit(m_settings.get_int(settings_pack::dht_block_ratelimit)); + + m_refresh_timer.expires_after(seconds(5)); + 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"); + m_key_refresh_timer.expires_after(key_refresh); + 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*** %d nodes" + , int(m_nodes.size())); +#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 + , aux::is_v6(ep) ? 48 : 28); + m_counters.inc_stats_counter(counters::dht_messages_in); + + if (m_settings.get_bool(settings_pack::dht_ignore_dark_internet) && aux::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_context& ios + , aux::listen_socket_handle const& s, socket_manager* sock + , aux::session_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; + + std::int64_t const limit = m_settings.get_int(settings_pack::dht_upload_rate_limit); + + // allow 3 seconds worth of burst + std::int64_t const max_accrue = std::min(3 * limit, std::int64_t(std::numeric_limits::max())); + + if (delta >= seconds(3) + || delta >= microseconds(std::numeric_limits::max() / limit)) + { + m_send_quota = aux::numeric_cast(max_accrue); + return true; + } + + int const add = aux::numeric_cast(limit * total_microseconds(delta) / 1000000); + + if (max_accrue - m_send_quota < add) + { + m_send_quota = aux::numeric_cast(max_accrue); + return true; + } + else + { + // add any new quota we've accrued since last time + m_send_quota += add; + } + TORRENT_ASSERT(m_send_quota <= max_accrue); + 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(lt::version_minor < 16, "version number not supported by DHT"); + static_assert(lt::version_tiny < 16, "version number not supported by DHT"); + static char const ver[] = {'L', 'T' + , lt::version_major, (lt::version_minor << 4) | lt::version_tiny}; + e["v"] = std::string(ver, ver+ 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 + , aux::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..f2c1964 --- /dev/null +++ b/src/kademlia/dos_blocker.cpp @@ -0,0 +1,114 @@ +/* + +Copyright (c) 2010, 2014-2019, Arvid Norberg +Copyright (c) 2016-2017, 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/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..a2d63bc --- /dev/null +++ b/src/kademlia/ed25519.cpp @@ -0,0 +1,127 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, 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 +#include + +namespace libtorrent { namespace dht { + + std::array ed25519_create_seed() + { + std::array seed; + aux::crypto_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()); + + lt::aux::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()); + + lt::aux::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 lt::aux::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()); + + lt::aux::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()); + + lt::aux::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()); + + lt::aux::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..afd8913 --- /dev/null +++ b/src/kademlia/find_data.cpp @@ -0,0 +1,197 @@ +/* + +Copyright (c) 2006, Daniel Wallin +Copyright (c) 2006, 2008-2010, 2013-2017, 2019, Arvid Norberg +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 ncallback) + : traversal_algorithm(dht_node, target) + , m_nodes_callback(std::move(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 const nodes = m_node.m_table.find_node( + target(), 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..2a2ce9f --- /dev/null +++ b/src/kademlia/get_item.cpp @@ -0,0 +1,222 @@ +/* + +Copyright (c) 2013, Steven Siloti +Copyright (c) 2015, Thomas +Copyright (c) 2013-2019, Arvid Norberg +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 dcallback + , nodes_callback ncallback) + : find_data(dht_node, target, std::move(ncallback)) + , m_data_callback(std::move(dcallback)) + , m_immutable(true) +{ +} + +get_item::get_item( + node& dht_node + , public_key const& pk + , span salt + , data_callback dcallback + , nodes_callback ncallback) + : find_data(dht_node, item_target_id(salt, pk), std::move(ncallback)) + , m_data_callback(std::move(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..82c7ee3 --- /dev/null +++ b/src/kademlia/get_peers.cpp @@ -0,0 +1,332 @@ +/* + +Copyright (c) 2006, Daniel Wallin +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016-2017, Pavel Pimenov +Copyright (c) 2016-2017, 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 // 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 + && aux::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(aux::read_v4_endpoint(peers)); + } + else + { + // assume it's uTorrent/libtorrent format + peer_list = aux::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 dcallback + , nodes_callback ncallback + , bool noseeds) + : find_data(dht_node, target, std::move(ncallback)) + , m_data_callback(std::move(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& target + , data_callback dcallback + , nodes_callback ncallback + , bool noseeds) + : get_peers(dht_node, target, std::move(dcallback), std::move(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..de7fe02 --- /dev/null +++ b/src/kademlia/item.cpp @@ -0,0 +1,212 @@ +/* + +Copyright (c) 2013-2019, Arvid Norberg +Copyright (c) 2015-2016, Steven Siloti +Copyright (c) 2016-2017, 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 // 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_mutable(true) +{} + +item::item(entry v) + : m_value(std::move(v)) +{} + +item::item(bdecode_node const& v) +{ + // 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..d15a05f --- /dev/null +++ b/src/kademlia/msg.cpp @@ -0,0 +1,138 @@ +/* + +Copyright (c) 2015-2016, Steven Siloti +Copyright (c) 2016, 2018-2019, Arvid Norberg +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/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..0fc7b95 --- /dev/null +++ b/src/kademlia/node.cpp @@ -0,0 +1,1251 @@ +/* + +Copyright (c) 2006-2020, Arvid Norberg +Copyright (c) 2014-2018, Steven Siloti +Copyright (c) 2015-2019, Alden Torres +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016-2017, Pavel Pimenov +Copyright (c) 2019, Amir Abrams +Copyright (c) 2020, Fonic +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/session_settings.hpp" +#include "libtorrent/alert_types.hpp" // for dht_lookup +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/aux_/ip_helpers.hpp" // for is_v4 + +#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/dht_settings.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 const 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 nid.is_all_zeros() + ? generate_random_id() + : nid; + } + + 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 + , aux::session_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, aux::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(aux::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) +{ + aux::crypto_random_bytes(m_secret[0]); + aux::crypto_random_bytes(m_secret[1]); +} + +node::~node() = default; + +int node::branch_factor() const { return m_settings.get_int(settings_pack::dht_search_branching); } + +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; + std::string const address = addr.address().to_string(); + h1.update(address); + h1.update(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(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; + std::string const address = addr.address().to_string(); + h.update(address); + h.update(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]; + aux::crypto_random_bytes(m_secret[0]); +} + +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"); + + if (ext_ip && ext_ip.string_length() >= int(aux::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, aux::read_v6_address(ptr) + , m.addr.address()); + } + else if (ext_ip && ext_ip.string_length() >= int(aux::address_size(udp::v4()))) + { + char const* ptr = ext_ip.string_ptr(); + if (m_observer != nullptr) + m_observer->set_external_address(m_sock, aux::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.get_bool(settings_pack::dht_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.get_bool(settings_pack::dht_privacy_lookups) + ? std::make_shared(*this, info_hash, std::move(dcallback), std::move(ncallback), noseeds) + : std::make_shared(*this, info_hash, std::move(dcallback), std::move(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, std::move(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; +} + +dht_status node::status() const +{ + std::lock_guard l(m_mutex); + + dht_status ret; + ret.our_id = m_id; + ret.local_endpoint = make_udp(m_sock.get_local_endpoint()); + m_table.status(ret.table); + + for (auto const& r : m_running_requests) + { + ret.requests.emplace_back(); + dht_lookup& lookup = ret.requests.back(); + r->status(lookup); + } + return ret; +} + +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); + aux::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.get_bool(settings_pack::dht_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 const n = m_table.find_node(info_hash, {}); + 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 const n = wanted_node->m_table.find_node(info_hash, {}); + 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..9b62089 --- /dev/null +++ b/src/kademlia/node_entry.cpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2015, Steven Siloti +Copyright (c) 2015-2017, 2019, Arvid Norberg +Copyright (c) 2016-2017, 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/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..71fd43d --- /dev/null +++ b/src/kademlia/node_id.cpp @@ -0,0 +1,215 @@ +/* + +Copyright (c) 2006-2008, 2010-2020, Arvid Norberg +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016, 2018, 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 "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/node_entry.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/ip_helpers.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 (aux::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..dfbc627 --- /dev/null +++ b/src/kademlia/put_data.cpp @@ -0,0 +1,118 @@ +/* + +Copyright (c) 2006, Daniel Wallin +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016-2019, Arvid Norberg +Copyright (c) 2016-2018, 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 + +namespace libtorrent { namespace dht { + +put_data::put_data(node& dht_node, put_callback callback) + : traversal_algorithm(dht_node, {}) + , m_put_callback(std::move(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..2b6bd61 --- /dev/null +++ b/src/kademlia/refresh.cpp @@ -0,0 +1,107 @@ +/* + +Copyright (c) 2006, Daniel Wallin +Copyright (c) 2006, 2008-2010, 2013-2017, 2019, Arvid Norberg +Copyright (c) 2015, Thomas Yuan +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 +#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..bc255f6 --- /dev/null +++ b/src/kademlia/routing_table.cpp @@ -0,0 +1,1239 @@ +/* + +Copyright (c) 2006-2020, Arvid Norberg +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2015-2016, 2018, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2016, Angel Leon +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2016, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/invariant_check.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/aux_/array.hpp" + +using namespace std::placeholders; + +namespace libtorrent { namespace dht { + +constexpr find_nodes_flags_t routing_table::include_failed; + +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(aux::session_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.get_bool(settings_pack::dht_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); + + auto 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 + , aux::session_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.get_bool(settings_pack::dht_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_uint() ^ rhs.to_v4().to_uint()); + 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.get_bool(settings_pack::dht_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.get_bool(settings_pack::dht_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.get_bool(settings_pack::dht_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.get_bool(settings_pack::dht_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.get_int(settings_pack::dht_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. +std::vector routing_table::find_node(node_id const& target + , find_nodes_flags_t const options, int count) +{ + std::vector l; + 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 l; + + 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 l; + } + 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 l; + + 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 l; + + 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 l; + } + unsorted_start_idx = int(l.size()); + } + while (j != m_buckets.begin() && int(l.size()) < count); + + TORRENT_ASSERT(int(l.size()) <= count); + return l; +} + +#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..d787c9f --- /dev/null +++ b/src/kademlia/rpc_manager.cpp @@ -0,0 +1,522 @@ +/* + +Copyright (c) 2006-2017, 2019-2020, Arvid Norberg +Copyright (c) 2015, Thomas +Copyright (c) 2015, 2017, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2016-2017, Andrei Kurushin +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 (aux::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 {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 + , aux::session_settings const& settings + , routing_table& table + , aux::listen_socket_handle sock + , socket_manager* sock_man + , dht_logger* log) + : m_pool_allocator(sizeof(observer_storage), 10) + , m_sock(std::move(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(); + std::uint16_t const tid = transaction_id.size() != 2 ? std::uint64_t(0xffff) : aux::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.get_bool(settings_pack::dht_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 auto short_timeout = seconds(1); + constexpr auto timeout = seconds(15); + + // look for observers that have timed out + + if (m_transactions.empty()) return short_timeout; + + std::vector timeouts; + std::vector short_timeouts; + + time_duration ret = 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 >= 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 >= 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(duration_cast(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(0xffff)); + aux::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.get_bool(settings_pack::dht_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.emplace(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..dbb40a5 --- /dev/null +++ b/src/kademlia/sample_infohashes.cpp @@ -0,0 +1,161 @@ +/* + +Copyright (c) 2017, 2019-2020, Arvid Norberg +Copyright (c) 2017, Alden Torres +Copyright (c) 2020, Fonic +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 dcallback) + : traversal_algorithm(dht_node, target) + , m_data_callback(std::move(dcallback)) {} + +char const* sample_infohashes::name() const { return "sample_infohashes"; } + +void sample_infohashes::got_samples(sha1_hash const& nid + , time_duration interval + , int num, std::vector samples + , std::vector> nodes) +{ + if (m_data_callback) + { + m_data_callback(nid, 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(aux::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); + } + } + + 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] wrong or missing id value" + , algorithm()->id()); +#endif + timeout(); + return; + } + + 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( + node_id(id.string_ptr()), 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..7149e15 --- /dev/null +++ b/src/kademlia/traversal_algorithm.cpp @@ -0,0 +1,675 @@ +/* + +Copyright (c) 2006, Daniel Wallin +Copyright (c) 2006-2017, 2019, Arvid Norberg +Copyright (c) 2015-2016, Steven Siloti +Copyright (c) 2016-2017, Pavel Pimenov +Copyright (c) 2016-2018, 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 // 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().get_bool(settings_pack::dht_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 = aux::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_uint() & 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().get_bool(settings_pack::dht_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(aux::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/listen_socket_handle.cpp b/src/listen_socket_handle.cpp new file mode 100644 index 0000000..f05c517 --- /dev/null +++ b/src/listen_socket_handle.cpp @@ -0,0 +1,75 @@ +/* + +Copyright (c) 2017, Steven Siloti +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/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..17ddd52 --- /dev/null +++ b/src/lsd.cpp @@ -0,0 +1,340 @@ +/* + +Copyright (c) 2007-2012, 2014-2020, Arvid Norberg +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2016-2017, 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 // 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_context& 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_wait(udp::socket::wait_read + , 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_after(seconds(2 * retry_count)); + 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_wait(udp::socket::wait_read + , 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(); + m_disabled = true; +} + +} // libtorrent namespace diff --git a/src/magnet_uri.cpp b/src/magnet_uri.cpp new file mode 100644 index 0000000..6b2897d --- /dev/null +++ b/src/magnet_uri.cpp @@ -0,0 +1,391 @@ +/* + +Copyright (c) 2007-2010, 2012-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2018, 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/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 = "magnet:?"; + + if (handle.info_hashes().has_v1()) + { + ret += "xt=urn:btih:"; + ret += aux::to_hex(handle.info_hashes().v1); + } + + if (handle.info_hashes().has_v2()) + { + if (handle.info_hashes().has_v1()) ret += '&'; + ret += "xt=urn:btmh:1220"; + ret += aux::to_hex(handle.info_hashes().v2); + } + + 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 = "magnet:?"; + + if (info.info_hashes().has_v1()) + { + ret += "xt=urn:btih:"; + ret += aux::to_hex(info.info_hashes().v1); + } + + if (info.info_hashes().has_v2()) + { + if (info.info_hashes().has_v1()) ret += '&'; + ret += "xt=urn:btmh:1220"; + ret += aux::to_hex(info.info_hashes().v2); + } + + 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 + , void*) + { + add_torrent_params params; + error_code ec; + parse_magnet_uri(uri, params, ec); + params.storage_mode = storage_mode; + 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[2] = { false, 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 (string_equal_no_case(name, "dn"_sv)) // display name + { + error_code e; + display_name = unescape_string(value, e); + } + else if (string_equal_no_case(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 (string_equal_no_case(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 (string_equal_no_case(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:") + { + value = value.substr(9); + + sha1_hash info_hash; + if (value.size() == 40) aux::from_hex(value, 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_hashes.v1 = info_hash; + has_ih[0] = true; + } + else if (value.substr(0, 9) == "urn:btmh:") + { + value = value.substr(9); + + // hash must be sha256 + if (value.substr(0, 4) != "1220") + { + ec = errors::invalid_info_hash; + return; + } + + value = value.substr(4); + + if (value.size() != 64) + { + ec = errors::invalid_info_hash; + return; + } + aux::from_hex(value, p.info_hashes.v2.data()); + has_ih[1] = true; + } + } + else if (string_equal_no_case(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 (string_equal_no_case(name, "x.pe"_sv)) + { + 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 (string_equal_no_case(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[0] && !has_ih[1]) + { + ec = errors::missing_info_hash_in_uri; + return; + } + +#if TORRENT_ABI_VERSION < 3 + p.info_hash = p.info_hashes.get_best(); +#endif + 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..ebf9a3b --- /dev/null +++ b/src/merkle.cpp @@ -0,0 +1,454 @@ +/* + +Copyright (c) 2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2015, Mike Tzou +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_/merkle.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/bitfield.hpp" + +namespace libtorrent { + + int merkle_layer_start(int const layer) + { + TORRENT_ASSERT(layer >= 0); + TORRENT_ASSERT(layer < int(sizeof(int) * 8)); + return (1 << layer) - 1; + } + + int merkle_to_flat_index(int const layer, int const offset) + { + TORRENT_ASSERT(layer >= 0); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(layer < int(sizeof(int) * 8)); + return merkle_layer_start(layer) + offset; + } + + int merkle_get_parent(int const tree_node) + { + // node 0 doesn't have a parent + TORRENT_ASSERT(tree_node > 0); + return (tree_node - 1) / 2; + } + + int merkle_get_sibling(int const 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_get_first_child(int const tree_node) + { + return tree_node * 2 + 1; + } + + int merkle_get_first_child(int const tree_node, int depth) + { + return ((tree_node + 1) << depth) - 1; + } + + int merkle_num_nodes(int const leafs) + { + TORRENT_ASSERT(leafs > 0); + TORRENT_ASSERT(leafs <= (std::numeric_limits::max() / 2) + 1); + TORRENT_ASSERT((leafs & (leafs - 1)) == 0); + // This is a way to calculate: (leafs << 1) - 1 without requiring an extra + // bit in the far left. The first 1 we subtract is worth 2 after we + // multiply by 2, so by just adding back one, we have effectively + // subtracted one from the result of multiplying by 2 + return ((leafs - 1) << 1) + 1; + } + + int merkle_first_leaf(int num_leafs) + { + // num_leafs must be a power of 2 + TORRENT_ASSERT(((num_leafs - 1) & num_leafs) == 0); + TORRENT_ASSERT(num_leafs > 0); + return num_leafs - 1; + } + + int merkle_num_leafs(int const blocks) + { + TORRENT_ASSERT(blocks > 0); + TORRENT_ASSERT(blocks <= std::numeric_limits::max() / 2); + // round up to nearest 2 exponent + int ret = 1; + while (blocks > ret) ret <<= 1; + return ret; + } + + int merkle_num_layers(int leaves) + { + // leaves must be a power of 2 + TORRENT_ASSERT((leaves & (leaves - 1)) == 0); + int layers = 0; + while (leaves > 1) + { + ++layers; + leaves >>= 1; + } + return layers; + } + + void merkle_fill_tree(span tree, int const num_leafs) + { + merkle_fill_tree(tree, num_leafs, merkle_num_nodes(num_leafs) - num_leafs); + } + + void merkle_fill_tree(span tree, int const num_leafs, int level_start) + { + TORRENT_ASSERT(level_start >= 0); + TORRENT_ASSERT(num_leafs >= 1); + + int level_size = num_leafs; + while (level_size > 1) + { + int parent = merkle_get_parent(level_start); + for (int i = level_start; i < level_start + level_size; i += 2, ++parent) + { + hasher256 h; + h.update(tree[i]); + h.update(tree[i + 1]); + tree[parent] = h.final(); + } + level_start = merkle_get_parent(level_start); + level_size /= 2; + } + TORRENT_ASSERT(level_size == 1); + } + + void merkle_fill_partial_tree(span tree) + { + int const num_nodes = aux::numeric_cast(tree.size()); + // the tree size must be one less than a power of two + TORRENT_ASSERT(((num_nodes+1) & num_nodes) == 0); + + // we do two passes over the tree, first to compute all the missing + // "interior" hashes. Then to clear all the ones that don't have a + // parent (i.e. "orphan" hashes). We clear them since we can't validate + // them against the root, which mean they may be incorrect. + int const num_leafs = (num_nodes + 1) / 2; + int level_size = num_leafs; + int level_start = merkle_first_leaf(num_leafs); + while (level_size > 1) + { + level_start = merkle_get_parent(level_start); + level_size /= 2; + + for (int i = level_start; i < level_start + level_size; ++i) + { + int const child = merkle_get_first_child(i); + bool const zeros_left = tree[child].is_all_zeros(); + bool const zeros_right = tree[child + 1].is_all_zeros(); + if (zeros_left || zeros_right) continue; + hasher256 h; + h.update(tree[child]); + h.update(tree[child + 1]); + tree[i] = h.final(); + } + } + TORRENT_ASSERT(level_size == 1); + + int parent = 0; + for (int i = 1; i < int(tree.size()); i += 2, parent += 1) + { + if (tree[parent].is_all_zeros()) + { + // if the parent is all zeros, the validation chain up to the + // root is broken, and this cannot be validated + tree[i].clear(); + tree[i + 1].clear(); + } + else if (tree[i + 1].is_all_zeros()) + { + // if the sibling is all zeros, this hash cannot be validated + tree[i].clear(); + } + else if (tree[i].is_all_zeros()) + { + // if this hash is all zeros, the sibling hash cannot be validated + tree[i + 1].clear(); + } + } + } + + void merkle_clear_tree(span tree, int const num_leafs, int level_start) + { + TORRENT_ASSERT(num_leafs >= 1); + TORRENT_ASSERT(level_start >= 0); + TORRENT_ASSERT(level_start < tree.size()); + TORRENT_ASSERT(level_start + num_leafs <= tree.size()); + // the range of nodes must be within a single level + TORRENT_ASSERT(merkle_get_layer(level_start) == merkle_get_layer(level_start + num_leafs - 1)); + + int level_size = num_leafs; + for (;;) + { + for (int i = level_start; i < level_start + level_size; ++i) + tree[i].clear(); + if (level_size == 1) break; + level_start = merkle_get_parent(level_start); + level_size /= 2; + } + TORRENT_ASSERT(level_size == 1); + } + + // compute the merkle tree root, given the leaves and the has to use for + // padding + sha256_hash merkle_root(span const leaves, sha256_hash const& pad) + { + int const num_leafs = merkle_num_leafs(int(leaves.size())); + aux::vector merkle_tree; + return merkle_root_scratch(leaves, num_leafs, pad, merkle_tree); + } + + // compute the merkle tree root, given the leaves and the has to use for + // padding + sha256_hash merkle_root_scratch(span leaves + , int num_leafs, sha256_hash pad + , std::vector& scratch_space) + { + TORRENT_ASSERT(((num_leafs - 1) & num_leafs) == 0); + + scratch_space.resize(std::size_t(leaves.size() + 1) / 2); + TORRENT_ASSERT(num_leafs > 0); + + if (num_leafs == 1) return leaves[0]; + + while (num_leafs > 1) + { + int i = 0; + for (; i < int(leaves.size()) / 2; ++i) + { + scratch_space[std::size_t(i)] = hasher256() + .update(leaves[i * 2]) + .update(leaves[i * 2 + 1]) + .final(); + } + if (leaves.size() & 1) + { + // if we have an odd number of leaves, compute the boundary hash + // here, that spans both a payload-hash and a pad hash + scratch_space[std::size_t(i)] = hasher256() + .update(leaves[i * 2]) + .update(pad) + .final(); + ++i; + } + // we don't have to copy any pad hashes into memory, they are implied + // just keep track of the current layer's pad hash + pad = hasher256().update(pad).update(pad).final(); + + // step one level up + leaves = span(scratch_space.data(), i); + num_leafs /= 2; + } + + return scratch_space[0]; + } + + // returns the layer the given offset into the tree falls into. + // Layer 0 is the root of the tree, layer 1 is the two hashes below the + // root, and so on. + int merkle_get_layer(int idx) + { + TORRENT_ASSERT(idx >= 0); + int layer = 1; + while (idx > (1 << layer) - 2) layer++; + return layer - 1; + } + + // returns the start of the layer, offset `idx` falls into. + int merkle_get_layer_offset(int idx) + { + return idx - ((1 << merkle_get_layer(idx)) - 1); + } + + // generates the pad hash for the tree level with "pieces" nodes, given the + // full tree has "blocks" number of blocks. + sha256_hash merkle_pad(int blocks, int pieces) + { + TORRENT_ASSERT(blocks >= pieces); + sha256_hash ret{}; + while (pieces < blocks) + { + hasher256 h; + h.update(ret); + h.update(ret); + ret = h.final(); + pieces *= 2; + } + return ret; + } + + bool merkle_validate_and_insert_proofs(span target_tree + , int const target_node_idx, sha256_hash const& node, span uncle_hashes) + { + if (target_tree[target_node_idx] == node) + return true; + + if (!target_tree[target_node_idx].is_all_zeros()) + return false; + + if (uncle_hashes.empty()) + return false; + + int cursor = target_node_idx; + target_tree[cursor] = node; + for (auto const& proof : uncle_hashes) + { + int const proof_idx = merkle_get_sibling(cursor); + TORRENT_ASSERT(target_tree[proof_idx].is_all_zeros()); + target_tree[proof_idx] = proof; + int const left = std::min(proof_idx, cursor); + auto const hash = hasher256().update(target_tree[left]).update(target_tree[left + 1]).final(); + cursor = merkle_get_parent(cursor); + if (target_tree[cursor] == hash) return true; + if (!target_tree[cursor].is_all_zeros()) + break; + target_tree[cursor] = hash; + } + + // we get here if we never reached a known hash in the tree, i.e. the + // uncle_hashes failed to prove the specified node hash. + // we now need to clear up all the hashes we've inserted into the tree + int clear_cursor = target_node_idx; + while (clear_cursor > cursor) + { + int const proof_idx = merkle_get_sibling(clear_cursor); + target_tree[clear_cursor].clear(); + target_tree[proof_idx].clear(); + clear_cursor = merkle_get_parent(clear_cursor); + } + return false; + } + + bool merkle_validate_node(sha256_hash const& left, sha256_hash const& right + , sha256_hash const& parent) + { + hasher256 h; + h.update(left); + h.update(right); + return (h.final() == parent); + } + + void merkle_validate_copy(span const src + , span const dst, sha256_hash const& root + , bitfield& verified_leafs) + { + TORRENT_ASSERT(src.size() == dst.size()); + int const num_leafs = int((dst.size() + 1) / 2); + if (src.empty()) return; + if (src[0] != root) return; + dst[0] = src[0]; + int const leaf_layer_start = int(src.size() - num_leafs); + for (int i = 0; i < leaf_layer_start; ++i) + { + if (dst[i].is_all_zeros()) continue; + int const left_child = merkle_get_first_child(i); + int const right_child = left_child + 1; + if (merkle_validate_node(src[left_child], src[right_child], dst[i])) + { + dst[left_child] = src[left_child]; + dst[right_child] = src[right_child]; + int const block_idx = left_child - leaf_layer_start; + if (left_child >= leaf_layer_start + && block_idx < verified_leafs.size()) + { + verified_leafs.set_bit(block_idx); + // the right child may be the first block of padding hash, + // in which case it's not part of the verified bitfield + if (block_idx + 1 < verified_leafs.size()) + verified_leafs.set_bit(block_idx + 1); + } + } + } + } + + bool merkle_validate_single_layer(span tree) + { + if (tree.size() == 1) return true; + int const num_leafs = int((tree.size() + 1) / 2); + int const end = int(tree.size()); + TORRENT_ASSERT((num_leafs & (num_leafs - 1)) == 0); + + int idx = merkle_first_leaf(num_leafs); + TORRENT_ASSERT(idx >= 1); + + while (idx < end) + { + if (!merkle_validate_node(tree[idx], tree[idx + 1], tree[merkle_get_parent(idx)])) + return false; + idx += 2; + } + return true; + } + + std::tuple merkle_find_known_subtree(span const tree + , int const block_index, int const num_valid_leafs) + { + // find the largest block of leafs from a single subtree we know the hashes of + int leafs_start = block_index; + int leafs_size = 1; + int const first_leaf = int(tree.size() / 2); + int root_index = merkle_get_sibling(first_leaf + block_index); + for (int i = block_index;; i >>= 1) + { + int const first_check_index = leafs_start + ((i & 1) ? -leafs_size : leafs_size); + for (int j = 0; j < std::min(leafs_size, num_valid_leafs - first_check_index); ++j) + { + if (tree[first_leaf + first_check_index + j].is_all_zeros()) + return std::make_tuple(leafs_start, leafs_size, root_index); + } + if (i & 1) leafs_start -= leafs_size; + leafs_size *= 2; + root_index = merkle_get_parent(root_index); + // if an inner node is known then its parent must be known too + // so if the root is known the next sibling subtree should already + // be computed if all of its leafs have valid hashes + if (!tree[root_index].is_all_zeros()) break; + TORRENT_ASSERT(root_index != 0); + TORRENT_ASSERT(leafs_start >= 0); + TORRENT_ASSERT(leafs_size <= merkle_num_leafs(num_valid_leafs)); + } + + TORRENT_ASSERT(leafs_start >= 0); + TORRENT_ASSERT(leafs_start < merkle_num_leafs(num_valid_leafs)); + TORRENT_ASSERT(leafs_start + leafs_size > block_index); + + return std::make_tuple(leafs_start, leafs_size, root_index); + } +} + diff --git a/src/merkle_tree.cpp b/src/merkle_tree.cpp new file mode 100644 index 0000000..fc84156 --- /dev/null +++ b/src/merkle_tree.cpp @@ -0,0 +1,1079 @@ +/* + +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/aux_/merkle_tree.hpp" +#include "libtorrent/aux_/merkle.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/ffs.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/aux_/invariant_check.hpp" + +namespace libtorrent { +namespace aux { + + merkle_tree::merkle_tree(int const num_blocks, int const blocks_per_piece, char const* r) + : m_root(r) + , m_num_blocks(num_blocks) + , m_blocks_per_piece_log(numeric_cast( + log2p1(numeric_cast(blocks_per_piece)))) + , m_mode(mode_t::empty_tree) + { + INVARIANT_CHECK; + + // blocks per piece must be an even power of 2 + TORRENT_ASSERT(((blocks_per_piece - 1) & blocks_per_piece) == 0); + TORRENT_ASSERT(m_root != nullptr); + TORRENT_ASSERT(this->blocks_per_piece() == blocks_per_piece); + } + + sha256_hash merkle_tree::root() const { return sha256_hash(m_root); } + + void merkle_tree::load_verified_bits(std::vector const& verified) + { + TORRENT_ASSERT(int(verified.size()) <= m_num_blocks); + TORRENT_ASSERT(m_block_verified.size() == m_num_blocks); + + // The verified bitfield may be invalid. If so, correct it to + // maintain the invariant of this class + int block_index = block_layer_start(); + for (int i = 0; i < int(verified.size()); ++i) + { + if (verified[std::size_t(i)] && has_node(block_index)) + m_block_verified.set_bit(i); + ++block_index; + } + } + + void merkle_tree::load_tree(span t, std::vector const& verified) + { + INVARIANT_CHECK; + if (t.empty()) return; + if (root() != t[0]) return; + if (size() != static_cast(t.size())) return; + + if (t.size() == 1) + { + // don't fully allocate a tree of 1 node. It's just the root and we + // have a special case representation for this + optimize_storage(); + return; + } + + allocate_full(); + + merkle_validate_copy(t, m_tree, root(), m_block_verified); + + load_verified_bits(verified); + + optimize_storage(); + optimize_storage_piece_layer(); + } + + void merkle_tree::clear() + { + m_tree.clear(); + m_tree.shrink_to_fit(); + m_block_verified.clear(); + m_mode = mode_t::empty_tree; + } + +namespace { + + // TODO: in C++20, use std::identity + struct identity + { + bool operator()(bool b) const { return b; } + }; +} + + void merkle_tree::load_sparse_tree(span t + , std::vector const& mask + , std::vector const& verified) + { + INVARIANT_CHECK; + TORRENT_ASSERT(mask.size() == size()); + if (size() != mask.size()) return; + + int const first_block = block_layer_start(); + int const end_block = first_block + m_num_blocks; + + TORRENT_ASSERT(first_block < int(mask.size())); + TORRENT_ASSERT(end_block <= int(mask.size())); + + // if the mask covers all blocks, go straight to block_layer + // mode, and validate + if (std::all_of(mask.begin() + first_block, mask.begin() + end_block, identity())) + { + // the index in t that points to first_block + auto const block_index = std::count_if(mask.begin(), mask.begin() + first_block, identity()); + + // discrepancy + if (t.size() < block_index + m_num_blocks) + return clear(); + + m_tree.assign(t.begin() + block_index, t.begin() + block_index + m_num_blocks); + m_mode = mode_t::block_layer; + + sha256_hash const r = merkle_root(m_tree); + // validation failed! + if (r != root()) clear(); + return; + } + + // if the piece layer is the same as the block layer, skip this next + // check + if (m_blocks_per_piece_log > 0) + { + int const first_piece = piece_layer_start(); + int const piece_count = num_pieces(); + int const end_piece = first_piece + piece_count; + + TORRENT_ASSERT(first_piece < int(mask.size())); + TORRENT_ASSERT(end_piece <= int(mask.size())); + + // if the mask convers all pieces, and nothing below that layer, go + // straight to piece_layer mode and validate + if (std::all_of(mask.begin() + first_piece, mask.begin() + end_piece, identity()) + + && std::all_of(mask.begin() + end_piece, mask.end(), std::logical_not<>())) + { + // the index in t that points to first_piece + auto const piece_index = std::count_if(mask.begin(), mask.begin() + first_piece, identity()); + // discrepancy + if (t.size() < piece_index + piece_count) + return clear(); + + m_tree.assign(t.begin() + piece_index, t.begin() + piece_index + piece_count); + m_mode = mode_t::piece_layer; + + sha256_hash const piece_layer_pad = merkle_pad(1 << m_blocks_per_piece_log, 1); + sha256_hash const r = merkle_root(m_tree, piece_layer_pad); + // validation failed! + if (r != root()) clear(); + return; + } + } + + // if the mask has only zeros, go straight to empty tree mode + if (t.empty() || std::none_of(mask.begin(), mask.end(), identity())) + return clear(); + + allocate_full(); + int cursor = 0; + for (std::size_t i = 0, end = mask.size(); i < end; ++i) + { + if (!mask[i]) continue; + if (cursor >= t.size()) break; + m_tree[int(i)] = t[cursor++]; + } + merkle_fill_partial_tree(m_tree); + + // this suggests that none of the hashes in the tree can be + // validated against the root. We effectively have an empty tree. + if (m_tree[0] != root()) + return clear(); + + load_verified_bits(verified); + + optimize_storage(); + } + + aux::vector merkle_tree::get_piece_layer() const + { + aux::vector ret; + + switch (m_mode) + { + case mode_t::uninitialized_tree: break; + case mode_t::empty_tree: break; + case mode_t::full_tree: + ret.assign(m_tree.begin() + piece_layer_start() + , m_tree.begin() + piece_layer_start() + num_pieces()); + break; + case mode_t::piece_layer: + { + ret = m_tree; + break; + } + case mode_t::block_layer: + { + ret.reserve(num_pieces()); + std::vector scratch_space; + + int const blocks_in_piece = blocks_per_piece(); + for (int b = 0; b < int(m_tree.size()); b += blocks_in_piece) + { + auto const leafs = span(m_tree).subspan(b); + ret.push_back(merkle_root_scratch(leafs, blocks_in_piece, sha256_hash{}, scratch_space)); + } + break; + } + } + return ret; + } + + // returns false if the piece layer fails to validate against the root hash + bool merkle_tree::load_piece_layer(span piece_layer) + { + INVARIANT_CHECK; + if (m_mode == mode_t::block_layer) return true; + + int const npieces = num_pieces(); + if (piece_layer.size() != npieces * sha256_hash::size()) return false; + + if (m_num_blocks == 1) + { + // special case for trees that only have a root hash + if (sha256_hash(piece_layer.data()) != root()) + return false; + m_mode = mode_t::empty_tree; + m_tree.clear(); + m_block_verified.clear(); + return true; + } + + sha256_hash const pad_hash = merkle_pad(1 << m_blocks_per_piece_log, 1); + + aux::vector pieces(npieces); + for (int n = 0; n < npieces; ++n) + pieces[n].assign(piece_layer.data() + n * sha256_hash::size()); + + if (merkle_root(pieces, pad_hash) != root()) + return false; + + // if there's only 1 block per piece, the piece layer is the same as the + // block layer, record that so we know there's no more work to do for + // this file + m_mode = m_blocks_per_piece_log == 0 ? mode_t::block_layer : mode_t::piece_layer; + m_tree = std::move(pieces); + + return true; + } + + // dest_start_idx points to the first *leaf* to be added. + // For example, T is the sub-tree to insert into the larger tree + // the uncle hashes are specified as 0, 1, providing proof that the subtree + // is valid, since the root node can be computed and validated. + // The root of the tree, R, is always known. + // R + // _ _ + // _ 1 _ _ + // _ _ _ _ T 0 _ _ + //_ _ _ _ _ _ _ _ T T _ _ _ _ _ _ + // ^ + // | + // dest_start_idx + boost::optional merkle_tree::add_hashes( + int const dest_start_idx + , piece_index_t::diff_type const file_piece_offset + , span hashes + , span uncle_hashes) + { + INVARIANT_CHECK; + + // as we set the hashes of interior nodes, we may be able to validate + // block hashes that we had since earlier. Any blocks that can be + // validated, and failed, are added to this list + add_hashes_result_t ret; + + // we already have all hashes + if (m_mode == mode_t::block_layer) + { + // since we're already on the block layer mode, we have the whole + // tree, and we've already reported any pieces as passing that may + // have existed in the tree when we completed it. At this point no + // more pieces should be reported as passed + return ret; + } + + allocate_full(); + + // TODO: this can be optimized by using m_tree as storage to fill this + // tree into, and then clear it if the hashes fail + int const leaf_count = merkle_num_leafs(int(hashes.size())); + aux::vector tree(merkle_num_nodes(leaf_count)); + std::copy(hashes.begin(), hashes.end(), tree.end() - leaf_count); + + // the end of a file is a special case, we may need to pad the leaf layer + if (leaf_count > hashes.size()) + { + int const leaf_layer_size = num_leafs(); + // assuming uncle_hashes lead all the way to the root, they tell us + // how many layers down we are + int const insert_layer_size = leaf_count << uncle_hashes.size(); + if (leaf_layer_size != insert_layer_size) + { + sha256_hash const pad_hash = merkle_pad(leaf_layer_size, insert_layer_size); + for (int i = int(hashes.size()); i < leaf_count; ++i) + tree[tree.end_index() - leaf_count + i] = pad_hash; + } + } + + merkle_fill_tree(tree, leaf_count); + + int const base_num_layers = merkle_num_layers(leaf_count); + + // this is the index of the node where we'll insert the root of the + // subtree (tree). It's also the hash the uncle_hashes are here to prove + // is valid. + int const insert_root_idx = dest_start_idx >> base_num_layers; + + // start with validating the proofs, and inserting them as we go. + if (!merkle_validate_and_insert_proofs(m_tree, insert_root_idx, tree[0], uncle_hashes)) + return {}; + + // first fill in the subtree of known hashes from the base layer + auto const num_leafs = merkle_num_leafs(m_num_blocks); + auto const first_leaf = merkle_first_leaf(num_leafs); + + // this is the start of the leaf layer of "tree". We'll use this + // variable to step upwards towards the root + int source_cursor = int(tree.size()) - leaf_count; + // the running index in the loop + int dest_cursor = dest_start_idx; + + // the number of tree levels in a piece hash. 0 means the block layer is + // the same as the piece layer + int const base = piece_levels(); + + // TODO: a piece outside of this range may also fail, if one of the uncle + // hashes is at the layer right above the block hashes + for (int layer_size = leaf_count; layer_size != 0; layer_size /= 2) + { + for (int i = 0; i < layer_size; ++i) + { + int const dst_idx = dest_cursor + i; + int const src_idx = source_cursor + i; + if (has_node(dst_idx)) + { + if (m_tree[dst_idx] != tree[src_idx]) + { + // this must be a block hash because inner nodes are not filled in until + // they can be verified. This assert ensures we're at the + // leaf layer of the file tree + TORRENT_ASSERT(dst_idx >= first_leaf); + + int const pos = dst_idx - first_leaf; + auto const piece = piece_index_t{pos >> m_blocks_per_piece_log} + file_piece_offset; + int const block = pos & ((1 << m_blocks_per_piece_log) - 1); + + TORRENT_ASSERT(pos < m_num_blocks); + if (!ret.failed.empty() && ret.failed.back().first == piece) + ret.failed.back().second.push_back(block); + else + ret.failed.emplace_back(piece, std::vector{block}); + + // now that this hash has been reported as failing, we + // can clear it. This will prevent it from being + // reported as failing again. + m_tree[dst_idx].clear(); + } + else if (dst_idx >= first_leaf) + { + // this covers the case where pieces are a single block. + // The common case is covered below + auto const piece = piece_index_t{(dst_idx - first_leaf) >> m_blocks_per_piece_log} + file_piece_offset; + + if (ret.passed.empty() || ret.passed.back() != piece) + ret.passed.push_back(piece); + } + } + + if (dst_idx >= first_leaf && dst_idx - first_leaf < m_num_blocks) + m_block_verified.set_bit(dst_idx - first_leaf); + + m_tree[dst_idx] = tree[src_idx]; + } + if (layer_size == 1) break; + dest_cursor = merkle_get_parent(dest_cursor); + source_cursor = merkle_get_parent(source_cursor); + } + + // if the piece layer and the block layer is the same, we have already + // identified all the failing hashes in the loop above. This is covering + // the cases where we just learned about piece level hashes and we can + // validate the block hashes for those pieces. + int const first_piece_idx = piece_layer_start(); + if (base != 0 + && dest_start_idx >= first_piece_idx + && dest_start_idx < first_piece_idx + num_pieces()) + { + int const blocks_in_piece = 1 << base; + + // it may now be possible to verify the hashes of previously received blocks + // try to verify as many child nodes of the received hashes as possible + for (int i = 0; i < int(hashes.size()); ++i) + { + int const piece = dest_start_idx + i; + if (piece - first_piece_idx >= num_pieces()) + break; + // the first block in this piece + int const block_idx = merkle_get_first_child(piece, base); + + int const block_end_idx = std::min(block_idx + blocks_in_piece, first_leaf + m_num_blocks); + if (std::any_of(m_tree.begin() + block_idx + , m_tree.begin() + block_end_idx + , [](sha256_hash const& h) { return h.is_all_zeros(); })) + continue; + + // TODO: instead of overwriting the root and comparing it + // against hashes[], write a functions that *validates* a tree + // by just filling it up to the level below the root and then + // validates it. + merkle_fill_tree(m_tree, blocks_in_piece, block_idx); + if (m_tree[piece] != hashes[i]) + { + merkle_clear_tree(m_tree, blocks_in_piece, block_idx); + // write back the correct hash + m_tree[piece] = hashes[i]; + TORRENT_ASSERT(blocks_in_piece == blocks_per_piece()); + + // an empty blocks vector indicates that we don't have the + // block hashes, and we can't know which block failed + // this will cause the block hashes to be requested + ret.failed.emplace_back(piece_index_t{piece - first_piece_idx} + file_piece_offset + , std::vector()); + } + else + { + ret.passed.push_back(piece_index_t{piece - first_piece_idx} + file_piece_offset); + // record that these block hashes are correct! + int const leafs_start = block_idx - block_layer_start(); + int const leafs_end = std::min(m_num_blocks, leafs_start + blocks_in_piece); + // TODO: this could be done more efficiently if bitfield had a function + // to set a range of bits + for (int k = leafs_start; k < leafs_end; ++k) + m_block_verified.set_bit(k); + } + TORRENT_ASSERT((piece - first_piece_idx) >= 0); + } + } + + optimize_storage(); + + return ret; + } + + std::tuple merkle_tree::set_block(int const block_index + , sha256_hash const& h) + { + INVARIANT_CHECK; + TORRENT_ASSERT(block_index < m_num_blocks); + + auto const num_leafs = merkle_num_leafs(m_num_blocks); + auto const first_leaf = merkle_first_leaf(num_leafs); + auto const block_tree_index = first_leaf + block_index; + + if (blocks_verified(block_index, 1)) + { + // if this blocks's hash is already known, check the passed-in hash against it + if (compare_node(block_tree_index, h)) + return std::make_tuple(set_block_result::ok, block_index, 1); + else + return std::make_tuple(set_block_result::block_hash_failed, block_index, 1); + } + + allocate_full(); + + m_tree[block_tree_index] = h; + + // to avoid wasting a lot of time hashing nodes only to discover they + // cannot be verified, check first to see if the root of the largest + // computable subtree is known + + // TODO: use structured binding in C++17 + int leafs_start; + int leafs_size; + int root_index; + std::tie(leafs_start, leafs_size, root_index) = + merkle_find_known_subtree(m_tree, block_index, m_num_blocks); + + // if the root node is unknown the hashes cannot be verified yet + if (m_tree[root_index].is_all_zeros()) + return std::make_tuple(set_block_result::unknown, leafs_start, leafs_size); + + // save the root hash because merkle_fill_tree will overwrite it + sha256_hash const root = m_tree[root_index]; + merkle_fill_tree(m_tree, leafs_size, first_leaf + leafs_start); + + if (root != m_tree[root_index]) + { + int const first_piece_idx = piece_layer_start(); + // hash failure, clear all the internal nodes + // not the block hashes though, except for the one we just added + if (root_index >= first_piece_idx) + { + // the whole piece failed the hash check. Clear all block hashes + // in this piece and report a hash failure + merkle_clear_tree(m_tree, leafs_size, first_leaf + leafs_start); + m_tree[root_index] = root; + return std::make_tuple(set_block_result::hash_failed, leafs_start, leafs_size); + } + else + { + // in this case, the root that we validated these hashes against + // were above the piece layer, so we don't really know whether + // this piece is invalid, or some other piece. So, just clear + // the internal nodes + merkle_clear_tree(m_tree, leafs_size / 2, merkle_get_parent(first_leaf + leafs_start)); + m_tree[root_index] = root; + return std::make_tuple(set_block_result::unknown, leafs_start, leafs_size); + } + } + + // TODO: this could be done more efficiently if bitfield had a function + // to set a range of bits + int const leafs_end = std::min(m_num_blocks, leafs_start + leafs_size); + for (int i = leafs_start; i < leafs_end; ++i) + m_block_verified.set_bit(i); + + // attempting to optimize storage is quite costly, only do it if we have + // a reason to believe it might have an effect + if (block_index == m_num_blocks - 1 || !m_tree[block_tree_index + 1].is_all_zeros()) + optimize_storage(); + + return std::make_tuple(set_block_result::ok, leafs_start, leafs_size); + } + + std::size_t merkle_tree::size() const + { + return static_cast(merkle_num_nodes(merkle_num_leafs(m_num_blocks))); + } + + int merkle_tree::num_pieces() const + { + int const ps = blocks_per_piece(); + TORRENT_ASSERT(ps > 0); + return (m_num_blocks + ps - 1) >> m_blocks_per_piece_log; + } + + int merkle_tree::block_layer_start() const + { + int const num_leafs = merkle_num_leafs(m_num_blocks); + TORRENT_ASSERT(num_leafs > 0); + return merkle_first_leaf(num_leafs); + } + + int merkle_tree::piece_layer_start() const + { + int const piece_layer_size = merkle_num_leafs(num_pieces()); + TORRENT_ASSERT(piece_layer_size > 0); + return merkle_first_leaf(piece_layer_size); + } + + int merkle_tree::num_leafs() const + { + return merkle_num_leafs(m_num_blocks); + } + + bool merkle_tree::has_node(int const idx) const + { + TORRENT_ASSERT(idx >= 0); + TORRENT_ASSERT(idx < int(size())); + switch (m_mode) + { + case mode_t::uninitialized_tree: + TORRENT_ASSERT_FAIL(); + return false; + case mode_t::empty_tree: return idx == 0; + case mode_t::full_tree: return !m_tree[idx].is_all_zeros(); + case mode_t::piece_layer: return idx < merkle_get_first_child(piece_layer_start()); + case mode_t::block_layer: return idx < block_layer_start() + m_num_blocks; + } + TORRENT_ASSERT_FAIL(); + return false; + } + + bool merkle_tree::compare_node(int const idx, sha256_hash const& h) const + { + switch (m_mode) + { + case mode_t::uninitialized_tree: + TORRENT_ASSERT_FAIL(); + return h.is_all_zeros(); + case mode_t::empty_tree: + return idx == 0 ? root() == h : h.is_all_zeros(); + case mode_t::full_tree: + return m_tree[idx] == h; + case mode_t::piece_layer: + { + int const first = piece_layer_start(); + int const piece_count = num_pieces(); + int const pieces_end = first + piece_count; + int const piece_layer_size = merkle_num_leafs(piece_count); + int const end = first + piece_layer_size; + if (idx >= end) + return h.is_all_zeros(); + if (idx >= pieces_end) + return h == merkle_pad(1 << m_blocks_per_piece_log, 1); + if (idx >= first) + return m_tree[idx - first] == h; + return (*this)[idx] == h; + } + case mode_t::block_layer: + { + int const first = block_layer_start(); + int const end = first + m_num_blocks; + if (idx >= end) + return h.is_all_zeros(); + if (idx >= first) + return m_tree[idx - first] == h; + return (*this)[idx] == h; + } + } + TORRENT_ASSERT_FAIL(); + return false; + } + + sha256_hash merkle_tree::operator[](int const idx) const + { + std::vector scratch; + return get_impl(idx, scratch); + } + + sha256_hash merkle_tree::get_impl(int idx, std::vector& scratch_space) const + { + switch (m_mode) + { + case mode_t::uninitialized_tree: + TORRENT_ASSERT_FAIL(); + return sha256_hash{}; + case mode_t::empty_tree: + return idx == 0 ? root() : sha256_hash{}; + case mode_t::full_tree: + return m_tree[idx]; + case mode_t::piece_layer: + case mode_t::block_layer: + { + int const start = (m_mode == mode_t::piece_layer) + ? piece_layer_start() + : block_layer_start(); + + if (m_mode == mode_t::piece_layer && idx >= merkle_get_first_child(start)) + return sha256_hash(); + + int layer_size = 1; + while (idx < start) + { + idx = merkle_get_first_child(idx); + layer_size *= 2; + } + + idx -= start; + if (idx >= m_tree.end_index()) + { + return merkle_pad( + (m_mode == mode_t::piece_layer) + ? layer_size << m_blocks_per_piece_log + : layer_size, 1); + } + + sha256_hash const pad_hash = (m_mode == mode_t::piece_layer) + ? merkle_pad(1 << m_blocks_per_piece_log, 1) + : sha256_hash{}; + auto const layer = span(m_tree) + .subspan(idx, std::min(m_tree.end_index() - idx, layer_size)); + + return merkle_root_scratch(layer, layer_size, pad_hash, scratch_space); + } + } + TORRENT_ASSERT_FAIL(); + return sha256_hash{}; + } + + std::vector merkle_tree::build_vector() const + { + INVARIANT_CHECK; + if (m_mode == mode_t::uninitialized_tree) return {}; + + std::vector ret(size()); + + switch (m_mode) + { + case mode_t::uninitialized_tree: break; + case mode_t::empty_tree: break; + case mode_t::full_tree: + ret.assign(m_tree.begin(), m_tree.end()); + break; + case mode_t::piece_layer: + { + int const piece_layer_size = merkle_num_leafs(num_pieces()); + sha256_hash const pad_hash = merkle_pad(1 << m_blocks_per_piece_log, 1); + int const start = merkle_first_leaf(piece_layer_size); + TORRENT_ASSERT(m_tree.end_index() <= piece_layer_size); + std::copy(m_tree.begin(), m_tree.end(), ret.begin() + start); + std::fill(ret.begin() + start + m_tree.end_index(), ret.begin() + start + piece_layer_size, pad_hash); + merkle_fill_tree(span(ret).subspan(0, merkle_num_nodes(piece_layer_size)) + , piece_layer_size); + break; + } + case mode_t::block_layer: + { + int const num_leafs = merkle_num_leafs(m_num_blocks); + sha256_hash const pad_hash{}; + int const start = merkle_first_leaf(num_leafs); + std::copy(m_tree.begin(), m_tree.end(), ret.begin() + start); + std::fill(ret.begin() + start + m_tree.end_index(), ret.begin() + start + num_leafs, sha256_hash{}); + merkle_fill_tree(ret, num_leafs); + break; + } + } + ret[0] = root(); + return ret; + } + + std::pair, aux::vector> merkle_tree::build_sparse_vector() const + { + if (m_mode == mode_t::uninitialized_tree) return {{}, {}}; + + aux::vector mask(size(), false); + std::vector ret; + switch (m_mode) + { + case mode_t::uninitialized_tree: break; + case mode_t::empty_tree: break; + case mode_t::full_tree: + for (int i = 0, end = m_tree.end_index(); i < end; ++i) + { + if (m_tree[i].is_all_zeros()) continue; + ret.push_back(m_tree[i]); + mask[i] = true; + } + break; + case mode_t::piece_layer: + { + int const piece_layer_size = merkle_num_leafs(num_pieces()); + for (int i = merkle_first_leaf(piece_layer_size), end = i + m_tree.end_index(); i < end; ++i) + mask[i] = true; + ret = m_tree; + break; + } + case mode_t::block_layer: + { + int const num_leafs = merkle_num_leafs(m_num_blocks); + for (int i = merkle_first_leaf(num_leafs), end = i + m_tree.end_index(); i < end; ++i) + mask[i] = true; + ret = m_tree; + break; + } + } + return {std::move(ret), std::move(mask)}; + } + + std::vector merkle_tree::verified_leafs() const + { + // note that for an empty tree (where the root is the full tree) and a + // tree where we have the piece layer, we also know all leaves in case + // the piece size is a single block. + switch (m_mode) + { + case mode_t::uninitialized_tree: + case mode_t::empty_tree: + return std::vector(std::size_t(m_num_blocks), m_num_blocks == 1); + case mode_t::piece_layer: + return std::vector(std::size_t(m_num_blocks), piece_levels() == 0); + case mode_t::block_layer: + return std::vector(std::size_t(m_num_blocks), true); + case mode_t::full_tree: + { + std::vector ret; + ret.resize(std::size_t(m_num_blocks), false); + for (int i = 0; i < m_block_verified.size(); ++i) + if (m_block_verified.get_bit(i)) ret[std::size_t(i)] = true; + return ret; + } + } + TORRENT_ASSERT_FAIL(); + return std::vector(); + } + + bool merkle_tree::is_complete() const + { + switch (m_mode) + { + case mode_t::uninitialized_tree: + return false; + case mode_t::empty_tree: + return m_num_blocks == 1; + case mode_t::piece_layer: + return piece_levels() == 0; + case mode_t::block_layer: + return true; + case mode_t::full_tree: + return !m_block_verified.empty() && m_block_verified.all_set(); + } + TORRENT_ASSERT_FAIL(); + return false; + } + + bool merkle_tree::blocks_verified(int block_idx, int num_blocks) const + { + TORRENT_ASSERT(num_blocks > 0); + TORRENT_ASSERT(block_idx < m_num_blocks); + TORRENT_ASSERT(block_idx + num_blocks <= m_num_blocks); + switch (m_mode) + { + case mode_t::uninitialized_tree: + return false; + case mode_t::empty_tree: + return m_num_blocks == 1; + case mode_t::piece_layer: + return piece_levels() == 0; + case mode_t::block_layer: + return true; + case mode_t::full_tree: + for (int i = block_idx; i < block_idx + num_blocks; ++i) + if (!m_block_verified.get_bit(i)) return false; + return true; + } + TORRENT_ASSERT_FAIL(); + return false; + } + + void merkle_tree::allocate_full() + { + INVARIANT_CHECK; + if (m_mode == mode_t::full_tree) return; + // if we already have the complete tree, we shouldn't be allocating it + // again. + TORRENT_ASSERT(m_mode != mode_t::block_layer); + m_tree = aux::vector(build_vector()); + m_mode = mode_t::full_tree; + m_block_verified.resize(m_num_blocks, false); + } + + void merkle_tree::optimize_storage() + { + INVARIANT_CHECK; + if (m_mode != mode_t::full_tree) return; + + if (m_num_blocks == 1) + { + // if this tree *just* has a root, no need to use any storage for + // nodes + m_tree.clear(); + m_tree.shrink_to_fit(); + m_mode = mode_t::empty_tree; + m_block_verified.clear(); + return; + } + + int const start = block_layer_start(); + if (m_block_verified.all_set()) + { + aux::vector new_tree(m_tree.begin() + start, m_tree.begin() + start + m_num_blocks); + + m_tree = std::move(new_tree); + m_mode = mode_t::block_layer; + m_block_verified.clear(); + return; + } + } + + void merkle_tree::optimize_storage_piece_layer() + { + INVARIANT_CHECK; + if (m_mode != mode_t::full_tree) return; + + // if we have *any* blocks, we can't transition into piece layer mode, + // since we would lose those hashes + int const piece_layer_size = merkle_num_leafs(num_pieces()); + if (m_blocks_per_piece_log > 0 + && merkle_validate_single_layer(span(m_tree).subspan(0, merkle_num_nodes(piece_layer_size))) + && std::all_of(m_tree.begin() + block_layer_start(), m_tree.end(), [](sha256_hash const& h) { return h.is_all_zeros(); }) + ) + { + int const start = piece_layer_start(); + aux::vector new_tree(m_tree.begin() + start, m_tree.begin() + start + num_pieces()); + + m_tree = std::move(new_tree); + m_mode = mode_t::piece_layer; + m_block_verified.clear(); + return; + } + } + + std::vector merkle_tree::get_hashes(int const base + , int const index, int const count, int const proof_layers) const + { + // given the full size of the tree, half of it, rounded up, are leaf nodes + int const base_layer_idx = merkle_num_layers(num_leafs()) - base; + int const base_start_idx = merkle_to_flat_index(base_layer_idx, index); + + int const layer_start_idx = base_start_idx; + + std::vector ret; + ret.reserve(std::size_t(count)); + + std::vector scratch_space; + + if (base == 0 && m_mode == mode_t::block_layer) + { + // this is an optimization + int const blocks_end = std::min(index + count, m_num_blocks); + for (int i = index; i < blocks_end; ++i) + ret.push_back(m_tree[i]); + // if fill the rest with padding + ret.resize(std::size_t(count)); + } + else + { + for (int i = layer_start_idx; i < layer_start_idx + count; ++i) + { + // the pad hashes are expected to be zero, they should not fail + // the request + if ((base != 0 || i < m_num_blocks + layer_start_idx - index) + && !has_node(i)) + return {}; + ret.push_back(get_impl(i, scratch_space)); + } + } + + // the number of layers up the tree which can be computed from the base layer hashes + // subtract one because the base layer doesn't count + int const base_tree_layers = merkle_num_layers(merkle_num_leafs(count)) - 1; + + int proof_idx = layer_start_idx; + for (int i = 0; i < proof_layers; ++i) + { + proof_idx = merkle_get_parent(proof_idx); + + // if this assert fire, the requester set proof_layers too high + // and it wasn't correctly validated + TORRENT_ASSERT(proof_idx > 0); + + if (i >= base_tree_layers) + { + int const sibling = merkle_get_sibling(proof_idx); + if (!has_node(proof_idx) || !has_node(sibling)) + return {}; + + ret.push_back(get_impl(sibling, scratch_space)); + } + } + + return ret; + } + +#if TORRENT_USE_INVARIANT_CHECKS + void merkle_tree::check_invariant() const + { + if (m_num_blocks == 1 && m_mode != mode_t::uninitialized_tree) + { + // a tree with only a single block is implicitly verified since we + // have the root hash (unless the tree is uninitialized) + TORRENT_ASSERT(blocks_verified(0, 1)); + } + switch (m_mode) + { + case mode_t::uninitialized_tree: + TORRENT_ASSERT(m_tree.empty()); + TORRENT_ASSERT(m_block_verified.empty()); + break; + case mode_t::empty_tree: + TORRENT_ASSERT(m_tree.empty()); + TORRENT_ASSERT(m_block_verified.empty()); + break; + case mode_t::full_tree: + { + TORRENT_ASSERT(m_tree[0] == root()); + TORRENT_ASSERT(m_block_verified.size() == m_num_blocks); + + auto const num_leafs = merkle_num_leafs(m_num_blocks); + + if (m_tree.size() == 1) break; + + // In all layers, except the block layer, all non-zero hashes + // must have a non-zero sibling and they must validate with + // their parent. + for (int i = 1; i < int(m_tree.size()) - num_leafs; i += 2) + { + if (m_tree[i].is_all_zeros()) + { + TORRENT_ASSERT(m_tree[i + 1].is_all_zeros()); + continue; + } + TORRENT_ASSERT(merkle_validate_node(m_tree[i], m_tree[i+1], m_tree[merkle_get_parent(i)])); + } + + // the block layer may contain invalid hashes, but if the + // corresponding bit in m_block_verified is 1, they must be + // correct, and match the block hashes. + // validate all blocks (that can be validated) + // since these are checked in pairs, we skip 2, to always + // consider the left side of the pair + auto const first_block = merkle_first_leaf(num_leafs); + for (int i = first_block, b = 0; i < first_block + m_num_blocks; i += 2, b += 2) + { + if (i + 1 == first_block + m_num_blocks) + { + // the edge case where there's an odd number of blocks + // and this is the last one + if (!m_block_verified.get_bit(b)) continue; + TORRENT_ASSERT(has_node(i)); + int const parent = merkle_get_parent(i); + TORRENT_ASSERT(merkle_validate_node(m_tree[i], sha256_hash(), m_tree[parent])); + } + else + { + TORRENT_ASSERT(m_block_verified.get_bit(b) == m_block_verified.get_bit(b + 1)); + if (!m_block_verified.get_bit(b)) continue; + TORRENT_ASSERT(has_node(i)); + + if (i + 1 < block_layer_start() + m_num_blocks) + TORRENT_ASSERT(has_node(i + 1)); + int const parent = merkle_get_parent(i); + TORRENT_ASSERT(merkle_validate_node(m_tree[i], m_tree[i + 1], m_tree[parent])); + } + } + // ensure padding is all zeros + for (int i = first_block + m_num_blocks; i < int(m_tree.size()); ++i) + TORRENT_ASSERT(m_tree[i].is_all_zeros()); + break; + } + case mode_t::piece_layer: + { + TORRENT_ASSERT(merkle_root(m_tree, merkle_pad(1 << m_blocks_per_piece_log, 1)) == root()); + TORRENT_ASSERT(m_block_verified.empty()); + break; + } + case mode_t::block_layer: + { + TORRENT_ASSERT(merkle_root(m_tree) == root()); + TORRENT_ASSERT(m_block_verified.empty()); + break; + } + } + } +#endif +} +} diff --git a/src/mmap.cpp b/src/mmap.cpp new file mode 100644 index 0000000..12cd56b --- /dev/null +++ b/src/mmap.cpp @@ -0,0 +1,742 @@ +/* + +Copyright (c) 2016, 2019-2020, Arvid Norberg +Copyright (c) 2019, Steven Siloti +Copyright (c) 2020, Tiger Wang +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + +#include "libtorrent/aux_/mmap.hpp" +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/error_code.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include + +#ifdef TORRENT_WINDOWS +#include "libtorrent/aux_/win_util.hpp" +#endif + +#if TORRENT_HAVE_MMAP +#include // for mmap +#include +#include // for open + +#include "libtorrent/aux_/disable_warnings_push.hpp" +auto const map_failed = MAP_FAILED; +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +namespace libtorrent { +namespace aux { + +namespace { + std::int64_t memory_map_size(open_mode_t const mode + , std::int64_t const file_size, file_handle const& fh) + { + // if we're opening the file in write-mode, we'll always truncate it to + // the right size, but in read mode, we should not map more than the + // file size + return (mode & open_mode::write) + ? file_size : std::min(std::int64_t(fh.get_size()), file_size); + } + +#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; + +#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, static_cast(&in), sizeof(in) + , out, sizeof(out), &returned_bytes, nullptr); + + 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); + } + +#ifndef TORRENT_WINRT + std::once_flag g_once_flag; + + 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); + } +#endif // TORRENT_WINRT + +#endif // TORRENT_WINDOWS + +} // anonymous + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + +namespace { + + DWORD file_access(open_mode_t const mode) + { + return (mode & open_mode::write) + ? GENERIC_WRITE | GENERIC_READ + : GENERIC_READ; + } + + DWORD file_create(open_mode_t const mode) + { + return (mode & open_mode::write) ? OPEN_ALWAYS : OPEN_EXISTING; + } + +#ifdef TORRENT_WINRT + + DWORD file_flags(open_mode_t const mode) + { + return ((mode & open_mode::no_cache) ? FILE_FLAG_WRITE_THROUGH : 0) + | ((mode & open_mode::random_access) ? 0 : FILE_FLAG_SEQUENTIAL_SCAN) + ; + } + + DWORD file_attributes(open_mode_t const mode) + { + return (mode & open_mode::hidden) ? FILE_ATTRIBUTE_HIDDEN : FILE_ATTRIBUTE_NORMAL; + } + + auto create_file(const native_path_string & name, open_mode_t const mode) + { + CREATEFILE2_EXTENDED_PARAMETERS Extended + { + sizeof(CREATEFILE2_EXTENDED_PARAMETERS), + file_attributes(mode), + file_flags(mode) + }; + + return CreateFile2(name.c_str() + , file_access(mode) + , FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE + , file_create(mode) + , &Extended); + } + +#else + + DWORD file_flags(open_mode_t const mode) + { + // 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 + return ((mode & open_mode::hidden) ? FILE_ATTRIBUTE_HIDDEN : FILE_ATTRIBUTE_NORMAL) + | ((mode & open_mode::no_cache) ? FILE_FLAG_WRITE_THROUGH : 0) + | ((mode & open_mode::random_access) ? 0 : FILE_FLAG_SEQUENTIAL_SCAN) + ; + } + + auto create_file(const native_path_string & name, open_mode_t const mode) + { + return CreateFileW(name.c_str() + , file_access(mode) + , FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE + , nullptr + , file_create(mode) + , file_flags(mode) + , nullptr); + } + +#endif + +} // anonymous + +file_handle::file_handle(string_view name, std::int64_t const size + , open_mode_t const mode) + : m_fd(create_file(convert_to_native_path_string(name.to_string()), mode)) + , m_open_mode(mode) +{ + if (m_fd == invalid_handle) + { + throw_ex(error_code(GetLastError(), system_category()) + , operation_t::file_open); + } + + // try to make the file sparse if supported + // only set this flag if the file is opened for writing + if ((mode & aux::open_mode::sparse) && (mode & aux::open_mode::write)) + { + DWORD temp; + ::DeviceIoControl(m_fd, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &temp, nullptr); + } + + if ((mode & open_mode::truncate) + && !(mode & aux::open_mode::sparse) + && (mode & aux::open_mode::allow_set_file_valid_data)) + { + LARGE_INTEGER sz; + sz.QuadPart = size; + if (SetFilePointerEx(m_fd, sz, nullptr, FILE_BEGIN) == FALSE) + throw_ex(error_code(GetLastError(), system_category()), operation_t::file_seek); + + if (::SetEndOfFile(m_fd) == FALSE) + throw_ex(error_code(GetLastError(), system_category()), operation_t::file_truncate); + +#ifndef TORRENT_WINRT + // Enable privilege required by SetFileValidData() + // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilevaliddata + std::call_once(g_once_flag, acquire_manage_volume_privs); + + // if the user has permissions, avoid filling + // the file with zeroes, but just fill it with + // garbage instead + SetFileValidData(m_fd, size); +#endif + } +} + +#else + +namespace { + + int file_flags(open_mode_t const mode) + { + return ((mode & open_mode::write) + ? O_RDWR | O_CREAT : O_RDONLY) +#ifdef O_NOATIME + | ((mode & open_mode::no_atime) ? O_NOATIME : 0) +#endif + ; + } + + mode_t file_perms(open_mode_t const mode) + { + // rely on default umask to filter x and w permissions + // for group and others + mode_t permissions = S_IRUSR | S_IWUSR + | S_IRGRP | S_IWGRP + | S_IROTH | S_IWOTH; + + if ((mode & aux::open_mode::executable)) + permissions |= S_IXGRP | S_IXOTH | S_IXUSR; + + return permissions; + } + + int mmap_prot(open_mode_t const m) + { + return (m & open_mode::write) + ? (PROT_READ | PROT_WRITE) + : PROT_READ; + } + + int mmap_flags(open_mode_t const m) + { + TORRENT_UNUSED(m); + return + MAP_FILE | MAP_SHARED +#ifdef MAP_NOCACHE + | ((m & open_mode::no_cache) ? MAP_NOCACHE : 0) +#endif +#ifdef MAP_NOCORE + // BSD has a flag to exclude this region from core files + | MAP_NOCORE +#endif + ; + } + + int open_file(std::string const filename, open_mode_t const mode) + { + int ret = ::open(filename.c_str(), file_flags(mode), file_perms(mode)); + +#ifdef O_NOATIME + if (ret < 0 && (mode & open_mode::no_atime)) + { + // NOATIME may not be allowed for certain files, it's best-effort, + // so just try again without NOATIME + ret = ::open(filename.c_str() + , file_flags(mode & ~open_mode::no_atime), file_perms(mode)); + } +#endif + if (ret < 0) throw_ex(error_code(errno, system_category()), operation_t::file_open); + return ret; + } + +} // anonymous + +file_handle::file_handle(string_view name, std::int64_t const size + , open_mode_t const mode) + : m_fd(open_file(convert_to_native_path_string(name.to_string()), mode)) +{ +#ifdef DIRECTIO_ON + // for solaris + if (mode & open_mode::no_cache) + directio(m_fd, DIRECTIO_ON); +#endif + + if (mode & open_mode::truncate) + { + static_assert(sizeof(off_t) >= sizeof(size), "There seems to be a large-file issue in truncate()"); + if (ftruncate(m_fd, static_cast(size)) < 0) + { + int const err = errno; + ::close(m_fd); + throw_ex(error_code(err, system_category()), operation_t::file_truncate); + } + + if (!(mode & open_mode::sparse)) + { +#if TORRENT_HAS_FALLOCATE + // if you get a compile error here, you might want to + // define TORRENT_HAS_FALLOCATE to 0. + int const ret = ::posix_fallocate(m_fd, 0, size); + // posix_allocate fails with EINVAL in case the underlying + // filesystem does not support this operation + if (ret != 0 && ret != EINVAL) + { + ::close(m_fd); + throw_ex(error_code(ret, system_category()), operation_t::file_fallocate); + } +#elif defined F_ALLOCSP64 + flock64 fl64; + fl64.l_whence = SEEK_SET; + fl64.l_start = 0; + fl64.l_len = size; + if (fcntl(m_fd, F_ALLOCSP64, &fl64) < 0) + { + int const err = errno; + ::close(m_fd); + throw_ex(error_code(ret, system_category()), operation_t::file_fallocate); + } +#elif defined F_PREALLOCATE + fstore_t f = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, size, 0}; + if (fcntl(m_fd, F_PREALLOCATE, &f) < 0) + { + // It appears Apple's new filesystem (APFS) does not + // support this control message and fails with EINVAL + // if so, just skip it + if (errno != EINVAL) + { + if (errno != ENOSPC) + { + int const err = errno; + ::close(m_fd); + throw_ex(error_code(err, system_category()) + , operation_t::file_fallocate); + } + // ok, let's try to allocate non contiguous space then + f.fst_flags = F_ALLOCATEALL; + if (fcntl(m_fd, F_PREALLOCATE, &f) < 0) + { + int const err = errno; + ::close(m_fd); + throw_ex(error_code(err, system_category()) + , operation_t::file_fallocate); + } + } + } +#endif // F_PREALLOCATE + } + } + +#ifdef F_NOCACHE + // for BSD/Mac + if (mode & aux::open_mode::no_cache) + { + int yes = 1; + ::fcntl(m_fd, F_NOCACHE, &yes); + +#ifdef F_NODIRECT + // it's OK to temporarily cache written pages + ::fcntl(m_fd, F_NODIRECT, &yes); +#endif + } +#endif + +#if (TORRENT_HAS_FADVISE && defined POSIX_FADV_RANDOM) + if (mode & aux::open_mode::random_access) + { + // disable read-ahead + ::posix_fadvise(m_fd, 0, 0, POSIX_FADV_RANDOM); + } +#endif +} +#endif + +void file_handle::close() +{ + if (m_fd == invalid_handle) 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 + if ((m_open_mode & aux::open_mode::write) + && (m_open_mode & aux::open_mode::sparse) + && !is_sparse(m_fd)) + { + // 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; + ::DeviceIoControl(m_fd, FSCTL_SET_SPARSE, &b, sizeof(b) + , nullptr, 0, &temp, nullptr); + } +#endif + +#if TORRENT_HAVE_MMAP + ::close(m_fd); +#else + CloseHandle(m_fd); +#endif + m_fd = invalid_handle; +} + +file_handle::~file_handle() { close(); } + +file_handle& file_handle::operator=(file_handle&& rhs) & +{ + if (&rhs == this) return *this; + close(); + m_fd = rhs.m_fd; + rhs.m_fd = invalid_handle; + return *this; +} + +std::int64_t file_handle::get_size() const +{ +#if TORRENT_HAVE_MMAP + struct ::stat fs; + if (::fstat(fd(), &fs) != 0) + throw_ex(error_code(errno, system_category()), operation_t::file_stat); + return fs.st_size; +#else + LARGE_INTEGER file_size; + if (GetFileSizeEx(fd(), &file_size) == 0) + throw_ex(error_code(GetLastError(), system_category()), operation_t::file_stat); + return file_size.QuadPart; +#endif +} + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + +// ======== file mapping handle ======== +namespace { + +int map_protect(open_mode_t const m) +{ + return (m & open_mode::write) ? PAGE_READWRITE : PAGE_READONLY; +} + +} + +file_mapping_handle::file_mapping_handle(file_handle file, open_mode_t const mode + , std::int64_t const size) + : m_file(std::move(file)) + , m_mapping(CreateFileMapping(m_file.fd() + , nullptr + , map_protect(mode) + , size >> 32 + , size & 0xffffffff + , nullptr)) + { + TORRENT_ASSERT(size >= 0); + // CreateFileMapping will extend the underlying file to the specified // size. + // you can't map files of size 0, so we just set it to null. We + // still need to create the empty file. + if (size > 0 && m_mapping == nullptr) + throw_ex(error_code(GetLastError(), system_category())); + } + file_mapping_handle::~file_mapping_handle() { close(); } + + file_mapping_handle::file_mapping_handle(file_mapping_handle&& fm) + : m_file(std::move(fm.m_file)), m_mapping(fm.m_mapping) + { + fm.m_mapping = INVALID_HANDLE_VALUE; + } + + file_mapping_handle& file_mapping_handle::operator=(file_mapping_handle&& fm) & + { + if (&fm == this) return *this; + close(); + m_file = std::move(fm.m_file); + m_mapping = fm.m_mapping; + fm.m_mapping = INVALID_HANDLE_VALUE; + return *this; + } + + void file_mapping_handle::close() + { + if (m_mapping == INVALID_HANDLE_VALUE) return; + CloseHandle(m_mapping); + m_mapping = INVALID_HANDLE_VALUE; + } + +#endif // HAVE_MAP_VIEW_OF_FILE + +// =========== file mapping ============ + +#if TORRENT_HAVE_MMAP + +file_mapping::file_mapping(file_handle file, open_mode_t const mode, std::int64_t const file_size) + : m_size(memory_map_size(mode, file_size, file)) + , m_file(std::move(file)) + , m_mapping(m_size > 0 ? mmap(nullptr, static_cast(m_size) + , mmap_prot(mode), mmap_flags(mode), m_file.fd(), 0) + : nullptr) +{ + TORRENT_ASSERT(file_size >= 0); + // you can't create an mmap of size 0, so we just set it to null. We + // still need to create the empty file. + if (file_size > 0 && m_mapping == map_failed) + { + throw_ex(error_code(errno, system_category()), operation_t::file_mmap); + } + +#if TORRENT_USE_MADVISE + if (file_size > 0) + { + int const advise = ((mode & open_mode::random_access) ? 0 : MADV_SEQUENTIAL) +#ifdef MADV_DONTDUMP + // on versions of linux that support it, ask for this region to not be + // included in coredumps (mostly to make the coredumps more manageable + // with large disk caches) + // ignore errors here, since this is best-effort + | MADV_DONTDUMP +#endif +#ifdef MADV_NOCORE + // This is the BSD counterpart to exclude a range from core dumps + | MADV_NOCORE +#endif + ; + if (advise != 0) + madvise(m_mapping, static_cast(m_size), advise); + } +#endif +} + +void file_mapping::close() +{ + if (m_mapping == nullptr) return; + munmap(m_mapping, static_cast(m_size)); + m_mapping = nullptr; +} + +#else + +namespace { +DWORD map_access(open_mode_t const m) +{ + return (m & open_mode::write) ? FILE_MAP_READ | FILE_MAP_WRITE : FILE_MAP_READ ; +} +} // anonymous + +file_mapping::file_mapping(file_handle file, open_mode_t const mode + , std::int64_t const file_size + , std::shared_ptr open_unmap_lock) + : m_size(memory_map_size(mode, file_size, file)) + , m_file(std::move(file), mode, m_size) + , m_open_unmap_lock(open_unmap_lock) + , m_mapping(MapViewOfFile(m_file.handle() + , map_access(mode), 0, 0, static_cast(m_size))) +{ + // you can't create an mmap of size 0, so we just set it to null. We + // still need to create the empty file. + if (m_size > 0 && m_mapping == nullptr) + throw_ex(error_code(GetLastError(), system_category()), operation_t::file_mmap); +} + +void file_mapping::flush() +{ + if (m_mapping == nullptr) return; + + // ignore errors, this is best-effort + FlushViewOfFile(m_mapping, static_cast(m_size)); +} + +void file_mapping::close() +{ + if (m_mapping == nullptr) return; + flush(); + std::lock_guard l(*m_open_unmap_lock); + UnmapViewOfFile(m_mapping); + m_mapping = nullptr; +} +#endif + +file_mapping::file_mapping(file_mapping&& rhs) + : m_size(rhs.m_size) + , m_file(std::move(rhs.m_file)) + , m_mapping(rhs.m_mapping) + { + TORRENT_ASSERT(m_mapping); + rhs.m_mapping = nullptr; + } + + file_mapping& file_mapping::operator=(file_mapping&& rhs) & + { + if (&rhs == this) return *this; + close(); + m_file = std::move(rhs.m_file); + m_size = rhs.m_size; + m_mapping = rhs.m_mapping; + rhs.m_mapping = nullptr; + return *this; + } + + file_mapping::~file_mapping() { close(); } + + file_view file_mapping::view() + { + return file_view(shared_from_this()); + } + + +void file_mapping::dont_need(span range) +{ + auto* const start = const_cast(range.data()); + auto const size = static_cast(range.size()); + +#if TORRENT_USE_MADVISE + int const advise = 0 +#if defined TORRENT_LINUX && defined MADV_COLD + | MADV_COLD +#elif !defined TORRENT_LINUX && defined MADV_DONTNEED + // note that MADV_DONTNEED is broken on Linux. It can destroy data. We + // cannot use it + | MADV_DONTNEED +#endif + ; + + if (advise) + ::madvise(start, size, advise); +#endif +#ifndef TORRENT_WINDOWS + ::msync(start, size, MS_INVALIDATE); +#else + TORRENT_UNUSED(start); + TORRENT_UNUSED(size); +#endif +} + +void file_mapping::page_out(span range) +{ +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + // ignore errors, this is best-effort + FlushViewOfFile(range.data(), static_cast(range.size())); +#else + + auto* const start = const_cast(range.data()); + auto const size = static_cast(range.size()); +#if TORRENT_USE_MADVISE && defined MADV_PAGEOUT + ::madvise(start, size, MADV_PAGEOUT); +#endif + + ::msync(start, size, MS_ASYNC); + +#endif // MAP_VIEW_OF_FILE +} + +} // aux +} // libtorrent + +#endif // HAVE_MMAP || HAVE_MAP_VIEW_OF_FILE + diff --git a/src/mmap_disk_io.cpp b/src/mmap_disk_io.cpp new file mode 100644 index 0000000..3c66efd --- /dev/null +++ b/src/mmap_disk_io.cpp @@ -0,0 +1,1858 @@ +/* + +Copyright (c) 2007-2012, 2014-2020, Arvid Norberg +Copyright (c) 2016-2019, Steven Siloti +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2018, gubatron +Copyright (c) 2018, Xiyue Deng +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + +#include "libtorrent/mmap_storage.hpp" +#include "libtorrent/mmap_disk_io.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/aux_/disk_buffer_pool.hpp" +#include "libtorrent/aux_/mmap_disk_job.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/platform_util.hpp" +#include "libtorrent/aux_/disk_job_pool.hpp" +#include "libtorrent/aux_/disk_io_thread_pool.hpp" +#include "libtorrent/aux_/store_buffer.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/aux_/alloca.hpp" +#include "libtorrent/aux_/array.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/aux_/file_view_pool.hpp" +#include "libtorrent/aux_/scope_end.hpp" +#include "libtorrent/aux_/storage_free_list.hpp" + +#ifdef TORRENT_WINDOWS +#include "signal_error_code.hpp" +#include +#endif + +#ifdef _WIN32 +#include "libtorrent/aux_/windows.hpp" +#include "libtorrent/aux_/win_util.hpp" +#endif + +#include +#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(aux::job_action_t 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 { +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 + + aux::open_mode_t file_mode_for_job(aux::mmap_disk_job* j) + { + aux::open_mode_t ret = aux::open_mode::read_only; + if (!(j->flags & disk_interface::sequential_access)) ret |= aux::open_mode::random_access; + return ret; + } + +#if TORRENT_USE_ASSERTS + bool valid_flags(disk_job_flags_t const flags) + { + return (flags & ~(disk_interface::force_copy + | disk_interface::sequential_access + | disk_interface::volatile_read + | disk_interface::v1_hash + | disk_interface::flush_piece)) + == disk_job_flags_t{}; + } +#endif +} // anonymous namespace + +using jobqueue_t = tailqueue; + +// this is a singleton consisting of the thread and a queue +// of disk io jobs +struct TORRENT_EXTRA_EXPORT mmap_disk_io final + : disk_interface +{ + mmap_disk_io(io_context& ios, settings_interface const&, counters& cnt); +#if TORRENT_USE_ASSERTS + ~mmap_disk_io() override; +#endif + + void settings_updated() override; + storage_holder new_torrent(storage_params const& params + , std::shared_ptr const& owner) override; + void remove_torrent(storage_index_t) override; + + void abort(bool wait) 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, span v2 + , disk_job_flags_t flags + , std::function handler) override; + void async_hash2(storage_index_t storage, piece_index_t piece, int offset, 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_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; + + void update_stats_counters(counters& c) const override; + + std::vector get_status(storage_index_t) const override; + + // this submits all queued up jobs to the thread + void submit_jobs() override; + + status_t do_partial_read(aux::mmap_disk_job* j); + status_t do_read(aux::mmap_disk_job* j); + status_t do_write(aux::mmap_disk_job* j); + status_t do_hash(aux::mmap_disk_job* j); + status_t do_hash2(aux::mmap_disk_job* j); + + status_t do_move_storage(aux::mmap_disk_job* j); + status_t do_release_files(aux::mmap_disk_job* j); + status_t do_delete_files(aux::mmap_disk_job* j); + status_t do_check_fastresume(aux::mmap_disk_job* j); + status_t do_rename_file(aux::mmap_disk_job* j); + status_t do_stop_torrent(aux::mmap_disk_job* j); + status_t do_file_priority(aux::mmap_disk_job* j); + status_t do_clear_piece(aux::mmap_disk_job* j); + + void call_job_handlers(); + +private: + + struct job_queue : aux::pool_thread_interface + { + explicit job_queue(mmap_disk_io& owner) : m_owner(owner) {} + + void notify_all() override + { + m_job_cond.notify_all(); + } + + void thread_fun(aux::disk_io_thread_pool& pool, executor_work_guard work) override + { + ADD_OUTSTANDING_ASYNC("mmap_disk_io::work"); + m_owner.thread_fun(*this, pool); + + // w's dtor releases the io_context 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 mmap_disk_io object may be destructed + TORRENT_UNUSED(work); + COMPLETE_ASYNC("mmap_disk_io::work"); + } + + mmap_disk_io& 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, aux::disk_io_thread_pool& pool); + + // returns true if the thread should exit + static bool wait_for_job(job_queue& jobq, aux::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& new_jobs); + + void fail_jobs_impl(storage_error const& e, jobqueue_t& src, jobqueue_t& dst); + + void perform_job(aux::mmap_disk_job* j, jobqueue_t& completed_jobs); + + // this queues up another job to be submitted + void add_job(aux::mmap_disk_job* j, bool user_add = true); + void add_fence_job(aux::mmap_disk_job* j, bool user_add = true); + + // called when a job cannot be queued. Immediate failure/abort + void job_fail_add(aux::mmap_disk_job* j); + + void execute_job(aux::mmap_disk_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(aux::mmap_disk_job* j); + aux::disk_io_thread_pool& pool_for_job(aux::mmap_disk_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; + + aux::disk_job_pool m_job_pool; + + // 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; + aux::disk_io_thread_pool m_generic_threads; + job_queue m_hash_io_jobs; + aux::disk_io_thread_pool m_hash_threads; + + // every write job is inserted into this map while it is in the job queue. + // It is removed after the write completes. This will let subsequent reads + // pull the buffers straight out of the queue instead of having to + // synchronize with the writing thread(s) + aux::store_buffer m_store_buffer; + + settings_interface const& m_settings; + + // LRU cache of open files + aux::file_view_pool m_file_pool; + + // disk cache + aux::disk_buffer_pool m_buffer_pool; + + // 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_context. Callbacks are + // posted on this in order to have them execute in + // the main thread. + io_context& 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 + aux::storage_free_list m_free_slots; + + std::atomic_flag m_jobs_aborted = ATOMIC_FLAG_INIT; + +#if TORRENT_USE_ASSERTS + int m_magic = 0x1337; +#endif +}; + +TORRENT_EXPORT std::unique_ptr mmap_disk_io_constructor( + io_context& ios, settings_interface const& sett, counters& cnt) +{ + return std::make_unique(ios, sett, cnt); +} + +// ------- mmap_disk_io ------ + + mmap_disk_io::mmap_disk_io(io_context& ios, settings_interface 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_file_pool(sett.get_int(settings_pack::file_pool_size)) + , m_buffer_pool(ios) + , m_stats_counters(cnt) + , m_ios(ios) + { + settings_updated(); + } + + std::vector mmap_disk_io::get_status(storage_index_t const st) const + { + return m_file_pool.get_status(st); + } + + storage_holder mmap_disk_io::new_torrent(storage_params const& params + , std::shared_ptr const& owner) + { + TORRENT_ASSERT(params.files.is_valid()); + + storage_index_t const idx = m_free_slots.new_index(m_torrents.end_index()); + auto storage = std::make_shared(params, m_file_pool); + storage->set_storage_index(idx); + storage->set_owner(owner); + if (idx == m_torrents.end_index()) + m_torrents.emplace_back(std::move(storage)); + else m_torrents[idx] = std::move(storage); + return storage_holder(idx, *this); + } + + void mmap_disk_io::remove_torrent(storage_index_t const idx) + { + TORRENT_ASSERT(m_torrents[idx] != nullptr); + m_torrents[idx].reset(); + m_free_slots.add(idx); + } + +#if TORRENT_USE_ASSERTS + mmap_disk_io::~mmap_disk_io() + { + DLOG("destructing mmap_disk_io\n"); + TORRENT_ASSERT(m_magic == 0x1337); + m_magic = 0xdead; + + // abort should have been triggered + TORRENT_ASSERT(m_abort); + + // there are not supposed to be any writes in-flight by now + TORRENT_ASSERT(m_store_buffer.size() == 0); + + // all torrents are supposed to have been removed by now + TORRENT_ASSERT(m_torrents.size() == m_free_slots.size()); + TORRENT_ASSERT(m_generic_threads.num_threads() == 0); + TORRENT_ASSERT(m_hash_threads.num_threads() == 0); + if (!m_generic_io_jobs.m_queued_jobs.empty()) + { + for (auto i = m_generic_io_jobs.m_queued_jobs.iterate(); i.get(); i.next()) + std::printf("generic job: %d\n", int(i.get()->action)); + } + if (!m_hash_io_jobs.m_queued_jobs.empty()) + { + for (auto i = m_hash_io_jobs.m_queued_jobs.iterate(); i.get(); i.next()) + std::printf("hash job: %d\n", int(i.get()->action)); + } + TORRENT_ASSERT(m_generic_io_jobs.m_queued_jobs.empty()); + TORRENT_ASSERT(m_hash_io_jobs.m_queued_jobs.empty()); + } +#endif + + void mmap_disk_io::abort(bool const wait) + { + DLOG("mmap_disk_io::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 |= aux::mmap_disk_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 mmap_disk_io::settings_updated() + { + TORRENT_ASSERT(m_magic == 0x1337); + m_buffer_pool.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); + int const num_hash_threads = m_settings.get_int(settings_pack::hashing_threads); + DLOG("set max threads(%d, %d)\n", num_threads, num_hash_threads); + + m_generic_threads.set_max_threads(num_threads); + m_hash_threads.set_max_threads(num_hash_threads); + } + + void mmap_disk_io::fail_jobs_impl(storage_error const& e, jobqueue_t& src, jobqueue_t& dst) + { + while (!src.empty()) + { + aux::mmap_disk_job* j = src.pop_front(); + TORRENT_ASSERT((j->flags & aux::mmap_disk_job::in_progress) || !j->storage); + + if (j->action == aux::job_action_t::write) + { + m_store_buffer.erase({j->storage->storage_index(), j->piece, j->d.io.offset}); + } + j->ret = status_t::fatal_disk_error; + j->error = e; + dst.push_back(j); + } + } + + namespace { + + typedef status_t (mmap_disk_io::*disk_io_fun_t)(aux::mmap_disk_job* j); + + // this is a jump-table for disk I/O jobs + std::array const job_functions = + {{ + &mmap_disk_io::do_read, + &mmap_disk_io::do_write, + &mmap_disk_io::do_hash, + &mmap_disk_io::do_hash2, + &mmap_disk_io::do_move_storage, + &mmap_disk_io::do_release_files, + &mmap_disk_io::do_delete_files, + &mmap_disk_io::do_check_fastresume, + &mmap_disk_io::do_rename_file, + &mmap_disk_io::do_stop_torrent, + &mmap_disk_io::do_file_priority, + &mmap_disk_io::do_clear_piece, + &mmap_disk_io::do_partial_read, + }}; + + } // anonymous namespace + + void mmap_disk_io::perform_job(aux::mmap_disk_job* j, jobqueue_t& completed_jobs) + { + TORRENT_ASSERT(j->next == nullptr); + TORRENT_ASSERT((j->flags & aux::mmap_disk_job::in_progress) || !j->storage); + +#if DEBUG_DISK_THREAD + { + std::unique_lock l(m_job_mutex); + + DLOG("perform_job job: %s ( %s%s) piece: %d offset: %d outstanding: %d\n" + , job_action_name[j->action] + , (j->flags & mmap_disk_job::fence) ? "fence ": "" + , (j->flags & mmap_disk_job::force_copy) ? "force_copy ": "" + , static_cast(j->piece), j->d.io.offset + , j->storage ? j->storage->num_outstanding_jobs() : -1); + } +#endif + + std::shared_ptr storage = j->storage; + + 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); + } + 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); + + j->ret = ret; + + completed_jobs.push_back(j); + } + + status_t mmap_disk_io::do_partial_read(aux::mmap_disk_job* j) + { + auto& buffer = boost::get(j->argument); + TORRENT_ASSERT(buffer); + + time_point const start_time = clock_type::now(); + + aux::open_mode_t const file_mode = file_mode_for_job(j); + iovec_t b = {buffer.data() + j->d.io.buffer_offset, j->d.io.buffer_size}; + + int const ret = j->storage->readv(m_settings, b + , j->piece, j->d.io.offset, file_mode, j->flags, j->error); + + TORRENT_ASSERT(ret >= 0 || j->error.ec); + 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 mmap_disk_io::do_read(aux::mmap_disk_job* j) + { + j->argument = disk_buffer_holder(m_buffer_pool, m_buffer_pool.allocate_buffer("send buffer"), default_block_size); + auto& buffer = boost::get(j->argument); + if (!buffer) + { + 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(); + + aux::open_mode_t const file_mode= file_mode_for_job(j); + iovec_t b = {buffer.data(), j->d.io.buffer_size}; + + int const ret = j->storage->readv(m_settings, b + , j->piece, j->d.io.offset, file_mode, j->flags, j->error); + + TORRENT_ASSERT(ret >= 0 || j->error.ec); + 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 mmap_disk_io::do_write(aux::mmap_disk_job* j) + { + time_point const start_time = clock_type::now(); + auto buffer = std::move(boost::get(j->argument)); + + iovec_t const b = { buffer.data(), j->d.io.buffer_size}; + aux::open_mode_t const file_mode= file_mode_for_job(j); + + m_stats_counters.inc_stats_counter(counters::num_writing_threads, 1); + + // the actual write operation + int const ret = j->storage->writev(m_settings, b + , j->piece, j->d.io.offset, file_mode, j->flags, j->error); + + 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.push_back({aux::time_now() + minutes(2), j->storage}); + } + + m_store_buffer.erase({j->storage->storage_index(), j->piece, j->d.io.offset}); + + return ret != j->d.io.buffer_size + ? status_t::fatal_disk_error : status_t::no_error; + } + + void mmap_disk_io::async_read(storage_index_t storage, peer_request const& r + , std::function handler + , disk_job_flags_t const flags) + { + TORRENT_ASSERT(valid_flags(flags)); + TORRENT_ASSERT(r.length <= default_block_size); + TORRENT_ASSERT(r.length > 0); + TORRENT_ASSERT(r.start >= 0); + + storage_error ec; + if (r.length <= 0 || r.start < 0) + { + // this is an invalid read request. + ec.ec = errors::invalid_request; + ec.operation = operation_t::file_read; + handler(disk_buffer_holder{}, ec); + return; + } + + // in case r.start is not aligned to a block, calculate that offset, + // since that's how the store_buffer is indexed. block_offset is the + // aligned offset to the first block this read touches. In the case the + // request is aligned, it's the same as r.start + int const block_offset = r.start - (r.start % default_block_size); + // this is the offset into the block that we're reading from + int const read_offset = r.start - block_offset; + + DLOG("async_read piece: %d block: %d (read-offset: %d)\n", static_cast(r.piece) + , block_offset / default_block_size, read_offset); + + disk_buffer_holder buffer; + + if (read_offset + r.length > default_block_size) + { + // This is an unaligned request spanning two blocks. One of the two + // blocks may be in the store buffer, or neither. + // If neither is in the store buffer, we can just issue a normal + // read job for the unaligned request. + + aux::torrent_location const loc1{storage, r.piece, block_offset}; + aux::torrent_location const loc2{storage, r.piece, block_offset + default_block_size}; + std::ptrdiff_t const len1 = default_block_size - read_offset; + + TORRENT_ASSERT(r.length > len1); + + int const ret = m_store_buffer.get2(loc1, loc2, [&](char const* buf1, char const* buf2) + { + buffer = disk_buffer_holder(m_buffer_pool, m_buffer_pool.allocate_buffer("send buffer"), r.length); + if (!buffer) + { + ec.ec = error::no_memory; + ec.operation = operation_t::alloc_cache_piece; + return 3; + } + + if (buf1) + std::memcpy(buffer.data(), buf1 + read_offset, std::size_t(len1)); + if (buf2) + std::memcpy(buffer.data() + len1, buf2, std::size_t(r.length - len1)); + return (buf1 ? 2 : 0) | (buf2 ? 1 : 0); + }); + + if (ret == 3) + { + // both sides were found in the store buffer and the read request + // was satisfied immediately + handler(std::move(buffer), ec); + return; + } + + if (ret != 0) + { + TORRENT_ASSERT(ret == 1 || ret == 2); + // only one side of the read request was found in the store + // buffer, and we need to issue a partial read for the remaining + // bytes + aux::mmap_disk_job* j = m_job_pool.allocate_job(aux::job_action_t::partial_read); + j->argument = std::move(buffer); + j->storage = m_torrents[storage]->shared_from_this(); + j->piece = r.piece; + j->d.io.offset = (ret == 1) ? r.start : block_offset + default_block_size; + j->d.io.buffer_size = std::uint16_t((ret == 1) ? len1 : r.length - len1); + j->d.io.buffer_offset = std::uint16_t((ret == 1) ? 0 : len1); + j->flags = flags; + j->callback = std::move(handler); + + 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])); + } + else + { + add_job(j); + } + return; + } + + // if we couldn't find any block in the store buffer, just post it + // as a normal read job + } + else + { + if (m_store_buffer.get({ storage, r.piece, block_offset }, [&](char const* buf) + { + buffer = disk_buffer_holder(m_buffer_pool, m_buffer_pool.allocate_buffer("send buffer"), r.length); + if (!buffer) + { + ec.ec = error::no_memory; + ec.operation = operation_t::alloc_cache_piece; + return; + } + + std::memcpy(buffer.data(), buf + read_offset, std::size_t(r.length)); + })) + { + handler(std::move(buffer), ec); + return; + } + } + + aux::mmap_disk_job* j = m_job_pool.allocate_job(aux::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->flags = flags; + j->callback = std::move(handler); + + 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])); + } + else + { + add_job(j); + } + } + + bool mmap_disk_io::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) + { + bool exceeded = false; + disk_buffer_holder buffer(m_buffer_pool, m_buffer_pool.allocate_buffer( + exceeded, o, "receive buffer"), default_block_size); + if (!buffer) aux::throw_ex(); + std::memcpy(buffer.data(), buf, aux::numeric_cast(r.length)); + + TORRENT_ASSERT(r.start % default_block_size == 0); + TORRENT_ASSERT(r.length <= default_block_size); + + aux::mmap_disk_job* j = m_job_pool.allocate_job(aux::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; + + m_store_buffer.insert({j->storage->storage_index(), j->piece, j->d.io.offset} + , boost::get(j->argument).data()); + + 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; + } + + add_job(j); + return exceeded; + } + + void mmap_disk_io::async_hash(storage_index_t const storage + , piece_index_t const piece, span const v2, disk_job_flags_t const flags + , std::function handler) + { + TORRENT_ASSERT(valid_flags(flags)); + aux::mmap_disk_job* j = m_job_pool.allocate_job(aux::job_action_t::hash); + j->storage = m_torrents[storage]->shared_from_this(); + j->piece = piece; + j->d.h.block_hashes = v2; + j->callback = std::move(handler); + j->flags = flags; + add_job(j); + } + + void mmap_disk_io::async_hash2(storage_index_t const storage + , piece_index_t const piece, int const offset, disk_job_flags_t const flags + , std::function handler) + { + TORRENT_ASSERT(valid_flags(flags)); + aux::mmap_disk_job* j = m_job_pool.allocate_job(aux::job_action_t::hash2); + j->storage = m_torrents[storage]->shared_from_this(); + j->piece = piece; + j->d.io.offset = offset; + j->callback = std::move(handler); + j->flags = flags; + add_job(j); + } + + void mmap_disk_io::async_move_storage(storage_index_t const storage + , std::string p, move_flags_t const flags + , std::function handler) + { + aux::mmap_disk_job* j = m_job_pool.allocate_job(aux::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 mmap_disk_io::async_release_files(storage_index_t const storage + , std::function handler) + { + aux::mmap_disk_job* j = m_job_pool.allocate_job(aux::job_action_t::release_files); + j->storage = m_torrents[storage]->shared_from_this(); + j->callback = std::move(handler); + + add_fence_job(j); + } + + void mmap_disk_io::abort_hash_jobs(storage_index_t const storage) + { + // abort outstanding hash jobs belonging to this torrent + std::unique_lock l(m_job_mutex); + + auto st = m_torrents[storage]->shared_from_this(); + // hash jobs + for (auto i = m_hash_io_jobs.m_queued_jobs.iterate(); i.get(); i.next()) + { + aux::mmap_disk_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 |= aux::mmap_disk_job::aborted; + } + } + + void mmap_disk_io::async_delete_files(storage_index_t const storage + , remove_flags_t const options + , std::function handler) + { + abort_hash_jobs(storage); + aux::mmap_disk_job* j = m_job_pool.allocate_job(aux::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 mmap_disk_io::async_check_files(storage_index_t const storage + , add_torrent_params const* resume_data + , aux::vector links + , std::function handler) + { + aux::mmap_disk_job* j = m_job_pool.allocate_job(aux::job_action_t::check_fastresume); + j->storage = m_torrents[storage]->shared_from_this(); + j->argument = resume_data; + j->callback = std::move(handler); + + aux::vector* links_vector = nullptr; + if (!links.empty()) links_vector = new aux::vector(std::move(links)); + j->d.links = links_vector; + + add_fence_job(j); + } + + void mmap_disk_io::async_rename_file(storage_index_t const storage + , file_index_t const index, std::string name + , std::function handler) + { + aux::mmap_disk_job* j = m_job_pool.allocate_job(aux::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 mmap_disk_io::async_stop_torrent(storage_index_t const storage + , std::function handler) + { + auto st = m_torrents[storage]->shared_from_this(); + abort_hash_jobs(storage); + + aux::mmap_disk_job* j = m_job_pool.allocate_job(aux::job_action_t::stop_torrent); + j->storage = st; + j->callback = std::move(handler); + add_fence_job(j); + } + + void mmap_disk_io::async_set_file_priority(storage_index_t const storage + , aux::vector prios + , std::function)> handler) + { + aux::mmap_disk_job* j = m_job_pool.allocate_job(aux::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 mmap_disk_io::async_clear_piece(storage_index_t const storage + , piece_index_t const index, std::function handler) + { + aux::mmap_disk_job* j = m_job_pool.allocate_job(aux::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. + // but it hardly seems worth the complexity and cost just for the edge + // case of receiving a corrupt piece + add_fence_job(j); + } + + status_t mmap_disk_io::do_hash(aux::mmap_disk_job* j) + { + // we're not using a cache. This is the simple path + // just read straight from the file + TORRENT_ASSERT(m_magic == 0x1337); + + bool const v1 = bool(j->flags & disk_interface::v1_hash); + bool const v2 = !j->d.h.block_hashes.empty(); + + int const piece_size = v1 ? j->storage->files().piece_size(j->piece) : 0; + int const piece_size2 = v2 ? j->storage->orig_files().piece_size2(j->piece) : 0; + int const blocks_in_piece = v1 ? (piece_size + default_block_size - 1) / default_block_size : 0; + int const blocks_in_piece2 = v2 ? j->storage->orig_files().blocks_in_piece2(j->piece) : 0; + aux::open_mode_t const file_mode = file_mode_for_job(j); + + TORRENT_ASSERT(!v2 || int(j->d.h.block_hashes.size()) >= blocks_in_piece2); + TORRENT_ASSERT(v1 || v2); + + hasher h; + int ret = 0; + int offset = 0; + int const blocks_to_read = std::max(blocks_in_piece, blocks_in_piece2); + for (int i = 0; i < blocks_to_read; ++i) + { + bool const v2_block = i < blocks_in_piece2; + + DLOG("do_hash: reading (piece: %d block: %d)\n", int(j->piece), i); + + time_point const start_time = clock_type::now(); + + std::ptrdiff_t const len = v1 ? std::min(default_block_size, piece_size - offset) : 0; + std::ptrdiff_t const len2 = v2_block ? std::min(default_block_size, piece_size2 - offset) : 0; + + hasher256 h2; + + if (!m_store_buffer.get({ j->storage->storage_index(), j->piece, offset } + , [&](char const* buf) + { + if (v1) + { + h.update({ buf, len }); + ret = int(len); + } + if (v2_block) + { + h2.update({ buf, len2 }); + ret = int(len2); + } + })) + { + if (v1) + { + // if we will call hashv2() in a bit, don't trigger a flush + // just yet, let hashv2() do it + auto const flags = v2_block ? (j->flags & ~disk_interface::flush_piece) : j->flags; + j->error.ec.clear(); + ret = j->storage->hashv(m_settings, h, len, j->piece, offset + , file_mode, flags, j->error); + if (ret < 0) break; + } + if (v2_block) + { + j->error.ec.clear(); + ret = j->storage->hashv2(m_settings, h2, len2, j->piece, offset + , file_mode, j->flags, j->error); + if (ret < 0) 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_blocks_read, blocks_to_read); + m_stats_counters.inc_stats_counter(counters::num_read_ops); + m_stats_counters.inc_stats_counter(counters::disk_hash_time, read_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, read_time); + } + + if (v2_block) + j->d.h.block_hashes[i] = h2.final(); + + if (ret <= 0) break; + + offset += default_block_size; + } + + if (v1) + j->d.h.piece_hash = h.final(); + return ret >= 0 ? status_t::no_error : status_t::fatal_disk_error; + } + + status_t mmap_disk_io::do_hash2(aux::mmap_disk_job* j) + { + TORRENT_ASSERT(m_magic == 0x1337); + + int const piece_size = j->storage->files().piece_size2(j->piece); + aux::open_mode_t const file_mode = file_mode_for_job(j); + + hasher256 h; + int ret = 0; + + DLOG("do_hash2: reading (piece: %d offset: %d)\n", int(j->piece), int(j->d.io.offset)); + + time_point const start_time = clock_type::now(); + + TORRENT_ASSERT(piece_size > j->d.io.offset); + std::ptrdiff_t const len = std::min(default_block_size, piece_size - j->d.io.offset); + + if (!m_store_buffer.get({ j->storage->storage_index(), j->piece, j->d.io.offset } + , [&](char const* buf) + { + h.update({ buf, len }); + ret = int(len); + })) + { + ret = j->storage->hashv2(m_settings, h, len, j->piece, j->d.io.offset + , file_mode, j->flags, j->error); + if (ret < 0) return status_t::fatal_disk_error; + } + + 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_hash_time, read_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, read_time); + } + + j->d.piece_hash2 = h.final(); + return ret >= 0 ? status_t::no_error : status_t::fatal_disk_error; + } + + status_t mmap_disk_io::do_move_storage(aux::mmap_disk_job* j) + { + // 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 + status_t ret; + std::string p; + std::tie(ret, p) = j->storage->move_storage(boost::get(j->argument) + , j->move_flags, j->error); + + boost::get(j->argument) = p; + return ret; + } + + status_t mmap_disk_io::do_release_files(aux::mmap_disk_job* j) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + j->storage->release_files(j->error); + return j->error ? status_t::fatal_disk_error : status_t::no_error; + } + + status_t mmap_disk_io::do_delete_files(aux::mmap_disk_job* j) + { + 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); + j->storage->delete_files(boost::get(j->argument), j->error); + return j->error ? status_t::fatal_disk_error : status_t::no_error; + } + + status_t mmap_disk_io::do_check_fastresume(aux::mmap_disk_job* j) + { + // 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 + auto const ret_flag = j->storage->initialize(m_settings, j->error); + if (j->error) return status_t::fatal_disk_error | ret_flag; + + // we must call verify_resume() unconditionally of the setting below, in + // order to set up the links (if present) + 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 | ret_flag; + + 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) + | ret_flag; + } + + return (verify_success + ? status_t::no_error + : status_t::need_full_check) + | ret_flag; + } + + status_t mmap_disk_io::do_rename_file(aux::mmap_disk_job* j) + { + // 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 mmap_disk_io::do_stop_torrent(aux::mmap_disk_job* j) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + j->storage->release_files(j->error); + return j->error ? status_t::fatal_disk_error : status_t::no_error; + } + + void mmap_disk_io::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, m_job_pool.read_jobs_in_use()); + c.set_value(counters::num_write_jobs, m_job_pool.write_jobs_in_use()); + c.set_value(counters::num_jobs, m_job_pool.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(); + + // gauges + c.set_value(counters::disk_blocks_in_use, m_buffer_pool.in_use()); + } + + status_t mmap_disk_io::do_file_priority(aux::mmap_disk_job* j) + { + j->storage->set_file_priority(m_settings + , 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 mmap_disk_io::do_clear_piece(aux::mmap_disk_job*) + { + // there's nothing to do here, by the time this is called the jobs for + // this storage has been completed since this is a fence job + return status_t::no_error; + } + + void mmap_disk_io::job_fail_add(aux::mmap_disk_job* j) + { + j->ret = status_t::fatal_disk_error; + j->error = storage_error(boost::asio::error::operation_aborted); + j->flags |= aux::mmap_disk_job::aborted; +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(j->job_posted == false); + j->job_posted = true; +#endif + std::lock_guard l(m_completed_jobs_mutex); + m_completed_jobs.push_back(j); + + if (!m_job_completions_in_flight) + { + DLOG("posting job handlers (%d)\n", m_completed_jobs.size()); + + post(m_ios, [this] { this->call_job_handlers(); }); + m_job_completions_in_flight = true; + } + } + + void mmap_disk_io::add_fence_job(aux::mmap_disk_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 + if (m_abort) + { + job_fail_add(j); + return; + } + + DLOG("add_fence:job: %s (outstanding: %d)\n" + , job_name(j->action) + , j->storage->num_outstanding_jobs()); + + TORRENT_ASSERT(j->storage); + m_stats_counters.inc_stats_counter(counters::num_fenced_read + static_cast(j->action)); + + int ret = j->storage->raise_fence(j, m_stats_counters); + if (ret == aux::disk_job_fence::fence_post_fence) + { + std::unique_lock l(m_job_mutex); + TORRENT_ASSERT((j->flags & aux::mmap_disk_job::in_progress) || !j->storage); + m_generic_io_jobs.m_queued_jobs.push_back(j); + l.unlock(); + + if (num_threads() == 0 && user_add) + immediate_execute(); + + return; + } + + if (num_threads() == 0 && user_add) + immediate_execute(); + } + + void mmap_disk_io::add_job(aux::mmap_disk_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 + if (m_abort) + { + job_fail_add(j); + return; + } + + // this happens for read jobs that get hung on pieces in the + // block cache, and then get issued + if (j->flags & aux::mmap_disk_job::in_progress) + { + std::unique_lock l(m_job_mutex); + TORRENT_ASSERT((j->flags & aux::mmap_disk_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 mmap_disk_io, + // 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 & aux::mmap_disk_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 mmap_disk_io, + // 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 mmap_disk_io::immediate_execute() + { + while (!m_generic_io_jobs.m_queued_jobs.empty()) + { + aux::mmap_disk_job* j = m_generic_io_jobs.m_queued_jobs.pop_front(); + execute_job(j); + } + } + + void mmap_disk_io::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 mmap_disk_io::execute_job(aux::mmap_disk_job* j) + { + jobqueue_t completed_jobs; + if (j->flags & aux::mmap_disk_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 mmap_disk_io::wait_for_job(job_queue& jobq, aux::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; + } + + using namespace std::literals::chrono_literals; + jobq.m_job_cond.wait_for(l, 1s); + } while (jobq.m_queued_jobs.empty()); + + threads.thread_active(); + } + + return false; + } + + void mmap_disk_io::thread_fun(job_queue& queue, aux::disk_io_thread_pool& pool) + { + std::thread::id const thread_id = std::this_thread::get_id(); + + set_thread_name("Disk"); + +#ifdef _WIN32 + using SetThreadInformation_t = BOOL (WINAPI*)(HANDLE, THREAD_INFORMATION_CLASS, LPVOID, DWORD); + auto SetThreadInformation = + aux::get_library_procedure("SetThreadInformation"); + if (SetThreadInformation) { + // MEMORY_PRIORITY_INFORMATION + struct MPI { + ULONG MemoryPriority; + }; +#ifndef MEMORY_PRIORITY_LOW + ULONG const MEMORY_PRIORITY_LOW = 2; +#endif + MPI info{MEMORY_PRIORITY_LOW}; + SetThreadInformation(GetCurrentThread(), ThreadMemoryPriority + , &info, sizeof(info)); + } +#endif + + 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); + + // we call close_oldest_file on the file_pool regularly. This is the next + // time we should call it + time_point next_close_oldest_file = min_time(); + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + time_point next_flush_file = min_time(); +#endif + + for (;;) + { + aux::mmap_disk_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 & aux::mmap_disk_job::in_progress) || !j->storage); + + if (&pool == &m_generic_threads && thread_id == pool.first_thread_id()) + { + 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 > next_close_oldest_file) + { + seconds const interval(m_settings.get_int(settings_pack::close_file_interval)); + if (interval <= seconds(0)) + { + // check again in one minute, in case the setting changed + next_close_oldest_file = now + minutes(1); + } + else + { + next_close_oldest_file = now + interval; + m_file_pool.close_oldest(); + } + } + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + if (now > next_flush_file) + { + // on windows we need to explicitly ask the operating system to flush + // dirty pages from time to time + m_file_pool.flush_next_file(); + next_flush_file = now + seconds(30); + } +#endif + } + + 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()). + + 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 mmap_disk_io::abort_jobs() + { + DLOG("mmap_disk_io::abort_jobs\n"); + + TORRENT_ASSERT(m_magic == 0x1337); + if (m_jobs_aborted.test_and_set()) return; + + // 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(); + TORRENT_ASSERT(m_magic == 0x1337); + } + + int mmap_disk_io::num_threads() const + { + return m_generic_threads.max_threads() + m_hash_threads.max_threads(); + } + + mmap_disk_io::job_queue& mmap_disk_io::queue_for_job(aux::mmap_disk_job* j) + { + if (m_hash_threads.max_threads() > 0 + && (j->action == aux::job_action_t::hash || j->action == aux::job_action_t::hash2)) + return m_hash_io_jobs; + else + return m_generic_io_jobs; + } + + aux::disk_io_thread_pool& mmap_disk_io::pool_for_job(aux::mmap_disk_job* j) + { + if (m_hash_threads.max_threads() > 0 + && (j->action == aux::job_action_t::hash || j->action == aux::job_action_t::hash2)) + return m_hash_threads; + else + return m_generic_threads; + } + + void mmap_disk_io::add_completed_jobs(jobqueue_t& jobs) + { + jobqueue_t completed = std::move(jobs); + jobqueue_t new_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 + add_completed_jobs_impl(completed, new_jobs); + TORRENT_ASSERT(completed.empty()); + completed.swap(new_jobs); + } while (!completed.empty()); + } + + void mmap_disk_io::add_completed_jobs_impl(jobqueue_t& jobs, jobqueue_t& completed) + { + jobqueue_t new_jobs; + int ret = 0; + for (auto i = jobs.iterate(); i.get(); i.next()) + { + aux::mmap_disk_job* j = i.get(); + TORRENT_ASSERT((j->flags & aux::mmap_disk_job::in_progress) || !j->storage); + + if (j->flags & aux::mmap_disk_job::fence) + { + m_stats_counters.inc_stats_counter( + counters::num_fenced_read + static_cast(j->action), -1); + } + + TORRENT_ASSERT(j->storage); + if (j->storage) + ret += j->storage->job_complete(j, new_jobs); + + TORRENT_ASSERT(ret == new_jobs.size()); + TORRENT_ASSERT(!(j->flags & aux::mmap_disk_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()) + { + while (!new_jobs.empty()) + { + aux::mmap_disk_job* j = new_jobs.pop_front(); + TORRENT_ASSERT((j->flags & aux::mmap_disk_job::in_progress) || !j->storage); + j->ret = status_t::fatal_disk_error; + j->error = storage_error(boost::asio::error::operation_aborted); + completed.push_back(j); + } + } + else + { + if (!new_jobs.empty()) + { + { + std::lock_guard l(m_job_mutex); + m_generic_io_jobs.m_queued_jobs.append(new_jobs); + } + + { + 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) + { + DLOG("posting job handlers (%d)\n", m_completed_jobs.size()); + + post(m_ios, [this] { this->call_job_handlers(); }); + m_job_completions_in_flight = true; + } + } + + // This is run in the network thread + void mmap_disk_io::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; + + aux::mmap_disk_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)); + aux::mmap_disk_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; + m_job_pool.free_jobs(to_delete.data(), int(to_delete.size())); + } + } + + if (cnt > 0) m_job_pool.free_jobs(to_delete.data(), cnt); + } +} + +#endif // HAVE_MMAP || HAVE_MAP_VIEW_OF_FILE diff --git a/src/mmap_disk_job.cpp b/src/mmap_disk_job.cpp new file mode 100644 index 0000000..a45d4cb --- /dev/null +++ b/src/mmap_disk_job.cpp @@ -0,0 +1,136 @@ +/* + +Copyright (c) 2010, 2014, 2016-2020, Arvid Norberg +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2020, 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/aux_/mmap_disk_job.hpp" +#include "libtorrent/disk_buffer_holder.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { +namespace aux { + + namespace { + struct caller_visitor : boost::static_visitor<> + { + explicit caller_visitor(mmap_disk_job& j) + : m_job(j) {} + + void operator()(mmap_disk_job::read_handler& h) const + { + if (!h) return; + h(std::move(boost::get(m_job.argument)) + , m_job.error); + } + + void operator()(mmap_disk_job::write_handler& h) const + { + if (!h) return; + h(m_job.error); + } + + void operator()(mmap_disk_job::hash_handler& h) const + { + if (!h) return; + h(m_job.piece, m_job.d.h.piece_hash, m_job.error); + } + + void operator()(mmap_disk_job::hash2_handler& h) const + { + if (!h) return; + h(m_job.piece, m_job.d.piece_hash2, m_job.error); + } + + void operator()(mmap_disk_job::move_handler& h) const + { + if (!h) return; + h(m_job.ret, std::move(boost::get(m_job.argument)) + , m_job.error); + } + + void operator()(mmap_disk_job::release_handler& h) const + { + if (!h) return; + h(); + } + + void operator()(mmap_disk_job::check_handler& h) const + { + if (!h) return; + h(m_job.ret, m_job.error); + } + + void operator()(mmap_disk_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()(mmap_disk_job::clear_piece_handler& h) const + { + if (!h) return; + h(m_job.piece); + } + + void operator()(mmap_disk_job::set_file_prio_handler& h) const + { + if (!h) return; + h(m_job.error, std::move(boost::get>(m_job.argument))); + } + + private: + mmap_disk_job& m_job; + }; + } + + constexpr disk_job_flags_t mmap_disk_job::fence; + constexpr disk_job_flags_t mmap_disk_job::in_progress; + constexpr disk_job_flags_t mmap_disk_job::aborted; + + mmap_disk_job::mmap_disk_job() + : argument(remove_flags_t{}) + , piece(0) + { + d.io.offset = 0; + d.io.buffer_size = 0; + } + + void mmap_disk_job::call_callback() + { + boost::apply_visitor(caller_visitor(*this), callback); + } +} +} diff --git a/src/mmap_storage.cpp b/src/mmap_storage.cpp new file mode 100644 index 0000000..235d828 --- /dev/null +++ b/src/mmap_storage.cpp @@ -0,0 +1,947 @@ +/* + +Copyright (c) 2003-2009, 2011, 2013-2020, Arvid Norberg +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2016, Vladimir Golovnev +Copyright (c) 2017-2018, Alden Torres +Copyright (c) 2017, 2019, Steven Siloti +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 "libtorrent/hasher.hpp" + +#include "try_signal.hpp" + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/mmap_storage.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/aux_/file_view_pool.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/hex.hpp" // to_hex + +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + +namespace libtorrent { + +namespace { + +error_code translate_error(std::system_error const& err, bool const write) +{ + // We don't really know why we failed to read or write. SIGBUS essentially + // means I/O failure. We assume that if we were writing, the failure was + // because of disk full + if (write) + { +#ifdef TORRENT_WINDOWS + if (err.code() == std::error_code(sig::seh_errors::in_page_error)) + return error_code(boost::system::errc::no_space_on_device, generic_category()); +#else + if (err.code() == std::error_code(sig::errors::bus)) + return error_code(boost::system::errc::no_space_on_device, generic_category()); +#endif + } + +#if BOOST_VERSION >= 107700 + +#ifdef TORRENT_WINDOWS + if (err.code() == std::error_code(sig::seh_errors::in_page_error)) + return error_code(boost::system::errc::io_error, generic_category()); +#else + if (err.code() == std::error_code(sig::errors::bus)) + return error_code(boost::system::errc::io_error, generic_category()); +#endif + + return err.code(); +#else + + return error_code(boost::system::errc::io_error, generic_category()); + +#endif +} +} // namespace + + + mmap_storage::mmap_storage(storage_params const& params + , aux::file_view_pool& pool) + : m_files(params.files) + , m_file_priority(params.priorities) + , m_save_path(complete(params.path)) + , m_part_file_name("." + aux::to_hex(params.info_hash) + ".parts") + , m_pool(pool) + , m_allocate_files(params.mode == storage_mode_allocate) + { + if (params.mapped_files) m_mapped_files = std::make_unique(*params.mapped_files); + + TORRENT_ASSERT(files().num_files() > 0); + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + m_file_open_unmap_lock.reset(new std::mutex[files().num_files()] + , [](std::mutex* o) { delete[] o; }); +#endif + } + + mmap_storage::~mmap_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 mmap_storage::need_partfile() + { + if (m_part_file) return; + + m_part_file = std::make_unique( + m_save_path, m_part_file_name + , files().num_pieces(), files().piece_length()); + } + + void mmap_storage::set_file_priority(settings_interface const& sett + , 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 + boost::optional f = open_file(sett, i, aux::open_mode::write, ec); + if (ec) + { + prio = m_file_priority; + return; + } + + if (m_part_file && use_partfile(i)) + { + try + { + m_part_file->export_file([&f](std::int64_t file_offset, span buf) + { + auto file_range = f->range().subspan(std::ptrdiff_t(file_offset)); + TORRENT_ASSERT(file_range.size() >= buf.size()); + sig::try_signal([&]{ + std::memcpy(const_cast(file_range.data()), buf.data() + , static_cast(buf.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; + } + } + catch (std::system_error const& err) + { + ec.file(i); + ec.operation = operation_t::partfile_write; + ec.ec = translate_error(err, true); + 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); + bool const file_exists = exists(fp, ec.ec); + if (ec.ec) + { + ec.file(i); + ec.operation = operation_t::file_stat; + prio = m_file_priority; + return; + } + use_partfile(i, !file_exists); +/* + auto f = open_file(sett, i, aux::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 mmap_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 mmap_storage::use_partfile(file_index_t const index, bool const b) + { + if (index >= m_use_partfile.end_index()) + { + // no need to extend this array if we're just setting it to "true", + // that's default already + if (b) return; + m_use_partfile.resize(static_cast(index) + 1, true); + } + m_use_partfile[index] = b; + } + + status_t mmap_storage::initialize(settings_interface const& sett, 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(); + status_t ret{}; + // 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; + + error_code err; + auto const size = m_stat_cache.get_filesize(i, fs, m_save_path, err); + if (!err && size > 0) + { + use_partfile(i, false); + if (size > fs.file_size(i)) + ret = ret | status_t::oversized_file; + } + else + { + // we may have earlier determined we *can't* use a partfile for + // this file, we need to be able to change our mind in case the + // file disappeared + use_partfile(i, true); + need_partfile(); + } + } + + aux::initialize_storage(fs, m_save_path, m_stat_cache, m_file_priority + , [&sett, this](file_index_t const file_index, storage_error& e) + { open_file(sett, file_index, aux::open_mode::write, e); } + , aux::create_symlink + , [&ret](file_index_t, std::int64_t) { ret = ret | status_t::oversized_file; } + , ec); + + // close files that were opened in write mode + m_pool.release(storage_index()); + return ret; + } + + bool mmap_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 == 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; + } + + void mmap_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 const 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 = std::make_unique(files()); } + m_mapped_files->rename_file(index, new_filename); + } + + void mmap_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 mmap_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 mmap_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); + } + + std::pair mmap_storage::move_storage(std::string save_path + , move_flags_t const flags, storage_error& ec) + { + m_pool.release(storage_index()); + + status_t ret; + auto move_partfile = [&](std::string const& new_save_path, error_code& e) + { + if (!m_part_file) return; + m_part_file->move_partfile(new_save_path, e); + }; + std::tie(ret, m_save_path) = aux::move_storage(files(), m_save_path, std::move(save_path) + , std::move(move_partfile), flags, ec); + + // clear the stat cache in case the new location has new files + m_stat_cache.clear(); + + return { ret, m_save_path }; + } + + int mmap_storage::readv(settings_interface const& sett + , span bufs + , piece_index_t const piece, int const offset + , aux::open_mode_t const mode + , disk_job_flags_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, mode, flags, &sett](file_index_t const file_index + , std::int64_t const file_offset + , span vec, storage_error& ec) + { + // reading from a pad file yields zeroes + if (files().pad_file_at(file_index)) return aux::read_zeroes(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; + } + + auto handle = open_file(sett, file_index, mode, ec); + if (ec) return -1; + + int ret = 0; + error_code e; + span file_range = handle->range(); + + // set this unconditionally in case the upper layer would like to treat + // short reads as errors + ec.operation = operation_t::file_read; + + try + { + if (file_range.size() > file_offset) + { + file_range = file_range.subspan(static_cast(file_offset)); + for (auto buf : vec) + { + if (file_range.empty()) break; + if (file_range.size() < buf.size()) buf = buf.first(file_range.size()); + + sig::try_signal([&]{ + std::memcpy(buf.data(), const_cast(file_range.data()) + , static_cast(buf.size())); + }); + + file_range = file_range.subspan(buf.size()); + ret += static_cast(buf.size()); + } + if (flags & disk_interface::volatile_read) + handle->dont_need(file_range); + if (flags & disk_interface::flush_piece) + handle->page_out(file_range); + } + } + catch (std::system_error const& err) + { + ec.file(file_index); + ec.ec = translate_error(err, false); + return -1; + } + + // 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 static_cast(ret); + }); + } + + int mmap_storage::writev(settings_interface const& sett + , span bufs + , piece_index_t const piece, int const offset + , aux::open_mode_t const mode + , disk_job_flags_t const flags + , storage_error& error) + { + return readwritev(files(), bufs, piece, offset, error + , [this, mode, flags, &sett](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); + + auto handle = open_file(sett, file_index + , aux::open_mode::write | mode, ec); + if (ec) return -1; + + int ret = 0; + error_code e; + span file_range = handle->range().subspan(static_cast(file_offset)); + + // set this unconditionally in case the upper layer would like to treat + // short reads as errors + ec.operation = operation_t::file_write; + + try + { + for (auto buf : vec) + { + TORRENT_ASSERT(file_range.size() >= buf.size()); + + sig::try_signal([&]{ + std::memcpy(const_cast(file_range.data()), buf.data(), static_cast(buf.size())); + }); + + file_range = file_range.subspan(buf.size()); + ret += static_cast(buf.size()); + } + + if (flags & disk_interface::volatile_read) + handle->dont_need(file_range); + if (flags & disk_interface::flush_piece) + handle->page_out(file_range); + } + catch (std::system_error const& err) + { + ec.file(file_index); + ec.ec = translate_error(err, true); + return -1; + } + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + m_pool.record_file_write(storage_index(), file_index, ret); +#endif + + if (e) + { + ec.ec = e; + ec.file(file_index); + return -1; + } + + return ret; + }); + } + + int mmap_storage::hashv(settings_interface const& sett + , hasher& ph, std::ptrdiff_t const len + , piece_index_t const piece, int const offset + , aux::open_mode_t const mode + , disk_job_flags_t const flags + , storage_error& error) + { +#ifdef TORRENT_SIMULATE_SLOW_READ + std::this_thread::sleep_for(seconds(1)); +#endif + char dummy = 0; + iovec_t dummy1 = {&dummy, len}; + span dummy2(&dummy1, 1); + + return readwritev(files(), dummy2, piece, offset, error + , [this, mode, flags, &ph, &sett](file_index_t const file_index + , std::int64_t const file_offset + , span vec, storage_error& ec) + { + auto const read_size = bufs_size(vec); + + if (files().pad_file_at(file_index)) + { + std::array zero_buf; + zero_buf.fill(0); + span zeroes = zero_buf; + for (std::ptrdiff_t left = read_size; left > 0; left -= zeroes.size()) + { + ph.update({zeroes.data(), std::min(zeroes.size(), left)}); + } + return read_size; + } + + if (file_index < m_file_priority.end_index() + && m_file_priority[file_index] == dont_download + && use_partfile(file_index)) + { + error_code e; + peer_request map = files().map_file(file_index, file_offset, 0); + int const ret = m_part_file->hashv(ph, read_size + , map.piece, map.start, e); + + if (e) + { + ec.ec = e; + ec.file(file_index); + ec.operation = operation_t::partfile_read; + return -1; + } + return ret; + } + + auto handle = open_file(sett, file_index, mode, ec); + if (ec) return -1; + + int ret = 0; + span file_range = handle->range(); + if (file_range.size() > file_offset) + { + file_range = file_range.subspan(std::ptrdiff_t(file_offset) + , std::min(std::ptrdiff_t(read_size), std::ptrdiff_t(file_range.size() - file_offset))); + + sig::try_signal([&]{ + ph.update({const_cast(file_range.data()), file_range.size()}); + }); + ret += static_cast(file_range.size()); + if (flags & disk_interface::volatile_read) + handle->dont_need(file_range); + if (flags & disk_interface::flush_piece) + handle->page_out(file_range); + } + + return ret; + }); + } + + int mmap_storage::hashv2(settings_interface const& sett + , hasher256& ph, std::ptrdiff_t const len + , piece_index_t const piece, int const offset + , aux::open_mode_t const mode + , disk_job_flags_t const flags + , storage_error& error) + { + std::int64_t const start_offset = static_cast(piece) * std::int64_t(files().piece_length()) + offset; + file_index_t const file_index = files().file_index_at_offset(start_offset); + std::int64_t const file_offset = start_offset - files().file_offset(file_index); + TORRENT_ASSERT(file_offset >= 0); + TORRENT_ASSERT(!files().pad_file_at(file_index)); + + if (file_index < m_file_priority.end_index() + && m_file_priority[file_index] == dont_download + && use_partfile(file_index)) + { + error_code e; + peer_request map = files().map_file(file_index, file_offset, 0); + int const ret = m_part_file->hashv2(ph, len + , map.piece, map.start, e); + + if (e) + { + error.ec = e; + error.file(file_index); + error.operation = operation_t::partfile_read; + return -1; + } + return ret; + } + + auto handle = open_file(sett, file_index, mode, error); + if (error) return -1; + + span file_range = handle->range(); + if (std::int64_t(file_range.size()) <= file_offset) + return 0; + file_range = file_range.subspan(std::ptrdiff_t(file_offset)); + file_range = file_range.first(std::min(std::ptrdiff_t(len), file_range.size())); + ph.update(file_range); + if (flags & disk_interface::volatile_read) + handle->dont_need(file_range); + if (flags & disk_interface::flush_piece) + handle->page_out(file_range); + + return static_cast(file_range.size()); + } + + // a wrapper around open_file_impl that, if it fails, makes sure the + // directories have been created and retries + boost::optional mmap_storage::open_file(settings_interface const& sett + , file_index_t const file + , aux::open_mode_t mode, storage_error& ec) const + { + if (mode & aux::open_mode::write + && !(mode & aux::open_mode::truncate)) + { + std::unique_lock l(m_file_created_mutex); + if (m_file_created.size() != files().num_files()) + m_file_created.resize(files().num_files(), false); + + // if we haven't created this file already, make sure to truncate it to + // its final size + mode |= (m_file_created[file] == false) ? aux::open_mode::truncate : aux::open_mode::read_only; + } + + if (files().file_flags(file) & file_storage::flag_executable) + mode |= aux::open_mode::executable; + + if (files().file_flags(file) & file_storage::flag_hidden) + mode |= aux::open_mode::hidden; + +#ifdef _WIN32 + if (sett.get_bool(settings_pack::enable_set_file_valid_data)) + { + mode |= aux::open_mode::allow_set_file_valid_data; + } +#endif + + boost::optional h = open_file_impl(sett, file, mode, ec); + if ((mode & aux::open_mode::write) + && (ec.ec == boost::system::errc::no_such_file_or_directory +#ifdef TORRENT_WINDOWS + // this is a workaround for improper handling of files on windows shared drives. + // if the directory on a shared drive does not exist, + // windows returns ERROR_IO_DEVICE instead of ERROR_FILE_NOT_FOUND + || ec.ec == error_code(ERROR_IO_DEVICE, system_category()) +#endif + )) + { + // 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) + { + // if the directory creation failed, don't try to open the file again + // but actually just fail + ec.file(file); + ec.operation = operation_t::mkdir; + return {}; + } + + h = open_file_impl(sett, file, mode, ec); + } + if (ec.ec) + { + ec.file(file); + return {}; + } + TORRENT_ASSERT(h); + + if (mode & aux::open_mode::truncate) + { + // remember that we've truncated this file, so we don't have to do it + // again + std::unique_lock l(m_file_created_mutex); + m_file_created.set_bit(file); + } + + // the optional should be set here + TORRENT_ASSERT(static_cast(h)); + return h; + } + + boost::optional mmap_storage::open_file_impl(settings_interface const& sett + , file_index_t file + , aux::open_mode_t mode + , storage_error& ec) const + { + TORRENT_ASSERT(!files().pad_file_at(file)); + if (!m_allocate_files) mode |= aux::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 |= aux::open_mode::sparse; + + if (sett.get_bool(settings_pack::no_atime_storage)) + { + mode |= aux::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 + auto const write_mode = sett.get_int(settings_pack::disk_io_write_mode); + if (write_mode == settings_pack::disable_os_cache + || write_mode == settings_pack::write_through) + { + mode |= aux::open_mode::no_cache; + } + + try { + return m_pool.open_file(storage_index(), m_save_path, file + , files(), mode +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + , std::shared_ptr(m_file_open_unmap_lock + , &m_file_open_unmap_lock.get()[int(file)]) +#endif + ); + } + catch (storage_error const& se) + { + ec = se; + ec.file(file); + TORRENT_ASSERT(ec); + return {}; + } + } + + bool mmap_storage::tick() + { + error_code ec; + if (m_part_file) m_part_file->flush_metadata(ec); + + return false; + } +} // namespace libtorrent + +#endif // TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE diff --git a/src/natpmp.cpp b/src/natpmp.cpp new file mode 100644 index 0000000..f95d710 --- /dev/null +++ b/src/natpmp.cpp @@ -0,0 +1,929 @@ +/* + +Copyright (c) 2007-2010, 2012, 2015-2020, Arvid Norberg +Copyright (c) 2016-2017, 2019, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2018, 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/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_context.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/aux_/ip_helpers.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 {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 {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_context& ios + , aux::portmap_callback& cb + , listen_socket_handle ls) + : m_callback(cb) + , m_socket(ios) + , m_send_timer(ios) + , m_refresh_timer(ios) + , m_ioc(ios) + , m_listen_handle(std::move(ls)) +{ + // 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(m_ioc, 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" + , ip.name, local_address.to_string().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::aux; + + // 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, m_listen_handle); +} +#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, m_listen_handle); + } + 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::crypto_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) + { + m_send_timer.cancel(); + error_code 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) + { + m_send_timer.cancel(); + error_code 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::aux; + + 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() + ? make_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() + ? make_address_v6(v4_mapped, m.external_address.to_v4()) + : m.external_address.to_v6(); + } + else if (aux::is_local(local_addr)) + { + external_addr = local_addr.is_v4() + ? make_address_v6(v4_mapped, address_v4()) + : address_v6(); + } + else if (local_addr.is_v4()) + { + external_addr = make_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_after(milliseconds(250 * m_retry_count)); + 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::aux; + 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; + } + + m_send_timer.cancel(); + + 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 + error_code ec; + if (m_version == version_pcp && !aux::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 = make_address_v4(v4_mapped, external_addr.to_v6()); + } + + 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, m_listen_handle); + } + 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_listen_handle); + } + + m_currently_mapping = port_mapping_t{-1}; + m->act = portmap_action::none; + m_send_timer.cancel(); + 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 + if (m_next_refresh >= port_mapping_t{}) m_refresh_timer.cancel(); + + ADD_OUTSTANDING_ASYNC("natpmp::mapping_expired"); + m_refresh_timer.expires_after(min_expire - now); + 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; + } + m_refresh_timer.cancel(); + m_currently_mapping = port_mapping_t{-1}; + update_mapping(port_mapping_t{}); +} + +} // namespace libtorrent diff --git a/src/packet_buffer.cpp b/src/packet_buffer.cpp new file mode 100644 index 0000000..491956c --- /dev/null +++ b/src/packet_buffer.cpp @@ -0,0 +1,195 @@ +/* + +Copyright (c) 2010-2012, 2014, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2017, 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/aux_/packet_buffer.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/invariant_check.hpp" + +namespace libtorrent { +namespace aux { + + 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..ef57aed --- /dev/null +++ b/src/parse_url.cpp @@ -0,0 +1,216 @@ +/* + +Copyright (c) 2008-2009, 2013-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, 2018, 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 "libtorrent/parse_url.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/string_view.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::min({ + std::find(start, url.end(), '/') + , std::find(start, url.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: + std::string path_component(start, url.end()); + if (path_component.empty() + || path_component.front() == '?' + || path_component.front() == '#') + { + path_component.insert(path_component.begin(), '/'); + } + + return std::make_tuple(std::move(protocol) + , std::move(auth) + , std::move(hostname) + , port + , path_component); + } + + // 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)); + } + + TORRENT_EXTRA_EXPORT bool is_idna(string_view hostname) + { + for (;;) + { + auto dot = hostname.find('.'); + string_view const label = (dot == string_view::npos) ? hostname : hostname.substr(0, dot); + if (label.size() >= 4 + && (label[0] == 'x' || label[0] == 'X') + && (label[1] == 'n' || label[1] == 'N') + && label.substr(2, 2) == "--"_sv) + return true; + if (dot == string_view::npos) return false; + hostname = hostname.substr(dot + 1); + } + } + + bool has_tracker_query_string(string_view query_string) + { + static string_view const tracker_args[] = { + "info_hash"_sv, "event"_sv, "port"_sv, "left"_sv, "key"_sv, + "uploaded"_sv, "downloaded"_sv, "corrupt"_sv, "peer_id"_sv + }; + while (!query_string.empty()) + { + string_view arg; + std::tie(arg, query_string) = split_string(query_string, '&'); + + auto const name = split_string(arg, '=').first; + for (auto const& tracker_arg : tracker_args) + { + if (string_equal_no_case(name, tracker_arg)) + return true; + } + } + return false; + } + +} diff --git a/src/part_file.cpp b/src/part_file.cpp new file mode 100644 index 0000000..26298df --- /dev/null +++ b/src/part_file.cpp @@ -0,0 +1,447 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016-2017, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 "libtorrent/aux_/storage_utils.hpp" // for iovec_t + +#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 path, std::string name + , int const num_pieces, int const piece_size) + : m_path(std::move(path)) + , m_name(std::move(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(aux::open_mode::read_only, ec); + if (ec) return; + + // parse header + std::vector header(static_cast(m_header_size)); + iovec_t b = header; + int const 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::aux; + + 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); + TORRENT_ASSERT(int(bufs.size()) + offset <= m_piece_size); + std::unique_lock l(m_mutex); + + auto f = open_file(aux::open_mode::write | aux::open_mode::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 const offset, error_code& ec) + { + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(int(bufs.size()) + offset <= m_piece_size); + 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(aux::open_mode::read_only | aux::open_mode::hidden, ec); + if (ec) return -1; + + return int(f.readv(slot_offset(slot) + offset, bufs, ec)); + } + + int part_file::hashv(hasher& ph + , std::ptrdiff_t const len + , piece_index_t const piece + , int const offset, error_code& ec) + { + return do_hashv(ph, len, piece, offset, ec); + } + + int part_file::hashv2(hasher256& ph + , std::ptrdiff_t const len + , piece_index_t const piece + , int const offset, error_code& ec) + { + return do_hashv(ph, len, piece, offset, ec); + } + + template + int part_file::do_hashv(Hasher& ph + , std::ptrdiff_t const len + , piece_index_t const piece + , int const offset, error_code& ec) + { + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(len >= 0); + TORRENT_ASSERT(int(len) + offset <= m_piece_size); + std::unique_lock l(m_mutex); + + auto const i = m_piece_map.find(piece); + if (i == m_piece_map.end()) + { + ec = error_code(boost::system::errc::no_such_file_or_directory + , boost::system::generic_category()); + return -1; + } + + slot_index_t const slot = i->second; + auto f = open_file(aux::open_mode::read_only | aux::open_mode::hidden, ec); + if (ec) return -1; + + l.unlock(); + + std::vector buffer(static_cast(len)); + iovec_t v = buffer; + std::int64_t const slot_offset = std::int64_t(m_header_size) + std::int64_t(static_cast(slot)) * m_piece_size; + int const ret = int(f.readv(slot_offset + offset, v, ec)); + ph.update(buffer); + return ret; + } + + file part_file::open_file(aux::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 & aux::open_mode::write) + && 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(aux::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(aux::open_mode::write | aux::open_mode::hidden, ec); + if (ec) return; + + std::vector header(static_cast(m_header_size)); + + using namespace libtorrent::aux; + + 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..bcea4c7 --- /dev/null +++ b/src/path.cpp @@ -0,0 +1,973 @@ +/* + +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2017-2019, Alden Torres +Copyright (c) 2020, Tiger Wang +Copyright (c) 2020, Kacper Michajłow +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 + +// 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/aux_/directory.hpp" +#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 +#include + +#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); + } + + void fill_file_status(file_status & s, LARGE_INTEGER file_size, DWORD file_attributes, FILETIME creation_time, FILETIME last_access, FILETIME last_write) + { + s.file_size = file_size.QuadPart; + s.ctime = file_time_to_posix(creation_time); + s.atime = file_time_to_posix(last_access); + s.mtime = file_time_to_posix(last_write); + + s.mode = (file_attributes & FILE_ATTRIBUTE_DIRECTORY) + ? file_status::directory + : (file_attributes & FILE_ATTRIBUTE_DEVICE) + ? file_status::character_special : file_status::regular_file; + } + + void fill_file_status(file_status & s, DWORD file_size_low, DWORD file_size_high, DWORD file_attributes, FILETIME creation_time, FILETIME last_access, FILETIME last_write) + { + LARGE_INTEGER file_size; + file_size.HighPart = static_cast(file_size_high); + file_size.LowPart = file_size_low; + + fill_file_status(s, file_size, file_attributes, creation_time, last_access, last_write); + } + +#ifdef TORRENT_WINRT + FILETIME to_file_time(LARGE_INTEGER i) + { + FILETIME time; + time.dwHighDateTime = i.HighPart; + time.dwLowDateTime = i.LowPart; + + return time; + } + + void fill_file_status(file_status & s, LARGE_INTEGER file_size, DWORD file_attributes, LARGE_INTEGER creation_time, LARGE_INTEGER last_access, LARGE_INTEGER last_write) + { + fill_file_status(s, file_size, file_attributes, to_file_time(creation_time), to_file_time(last_access), to_file_time(last_write)); + } +#endif +#endif +} // anonymous namespace + + native_path_string convert_to_native_path_string(std::string const& path) + { +#ifdef TORRENT_WINDOWS +#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 + return convert_to_wstring(path); +#endif +#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 + + WIN32_FILE_ATTRIBUTE_DATA data; + if (!GetFileAttributesExW(f.c_str(), GetFileExInfoStandard, &data)) + { + ec.assign(GetLastError(), system_category()); + TORRENT_ASSERT(ec); + return; + } + + // Fallback to GetFileInformationByHandle for symlinks + if (!(flags & dont_follow_links) && (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) + { +#ifdef TORRENT_WINRT + + CREATEFILE2_EXTENDED_PARAMETERS Extended + { + sizeof(CREATEFILE2_EXTENDED_PARAMETERS), + 0, // no file attributes + FILE_FLAG_BACKUP_SEMANTICS // in order to open a directory, we need the FILE_FLAG_BACKUP_SEMANTICS + }; + + const auto h = CreateFile2(f.c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ + | FILE_SHARE_WRITE, OPEN_EXISTING, &Extended); + + if (h == INVALID_HANDLE_VALUE) + { + ec.assign(GetLastError(), system_category()); + TORRENT_ASSERT(ec); + return; + } + + FILE_BASIC_INFO Basic; + FILE_STANDARD_INFO Standard; + + if ( + !GetFileInformationByHandleEx(h, FILE_INFO_BY_HANDLE_CLASS::FileBasicInfo, &Basic, sizeof(FILE_BASIC_INFO)) || + !GetFileInformationByHandleEx(h, FILE_INFO_BY_HANDLE_CLASS::FileStandardInfo, &Standard, sizeof(FILE_STANDARD_INFO)) + ) + { + ec.assign(GetLastError(), system_category()); + TORRENT_ASSERT(ec); + CloseHandle(h); + return; + } + CloseHandle(h); + + fill_file_status(*s, Standard.EndOfFile, Basic.FileAttributes, Basic.CreationTime, Basic.LastAccessTime, Basic.LastWriteTime); + return; + +#else + + // 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 handle_data; + if (!GetFileInformationByHandle(h, &handle_data)) + { + ec.assign(GetLastError(), system_category()); + TORRENT_ASSERT(ec); + CloseHandle(h); + return; + } + CloseHandle(h); + + fill_file_status(*s, handle_data.nFileSizeLow, handle_data.nFileSizeHigh, handle_data.dwFileAttributes, handle_data.ftCreationTime, handle_data.ftLastAccessTime, handle_data.ftLastWriteTime); + return; + +#endif + } + + fill_file_status(*s, data.nFileSizeLow, data.nFileSizeHigh, data.dwFileAttributes, data.ftCreationTime, data.ftLastAccessTime, data.ftLastWriteTime); + +#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; + } + + // make sure the _FILE_OFFSET_BITS define worked + // on this platform. It's supposed to make file + // related functions support 64-bit offsets. + static_assert(sizeof(ret.st_size) >= 8, "64 bit file operations are required"); + + 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 (f1 == f2) return; + +#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(), nullptr) == 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 +#ifndef TORRENT_WINRT + + 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 +#endif +#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 +#ifdef TORRENT_BEOS + // haiku returns EPERM when the filesystem doesn't support hard link + && errno != EPERM +#endif + ) + { + // 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 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) + { + auto const idx = static_cast(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 path_equal(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; + } + + // <0: lhs < rhs + // 0: lhs == rhs + // >0: lhs > rhs + int path_compare(string_view const lhs, string_view const lfile + , string_view const rhs, string_view const rfile) + { + for (auto lhs_elems = lsplit_path(lhs), rhs_elems = lsplit_path(rhs); + !lhs_elems.first.empty() || !rhs_elems.first.empty(); + lhs_elems = lsplit_path(lhs_elems.second), rhs_elems = lsplit_path(rhs_elems.second)) + { + if (lhs_elems.first.empty() || rhs_elems.first.empty()) + { + if (lhs_elems.first.empty()) lhs_elems.first = lfile; + if (rhs_elems.first.empty()) rhs_elems.first = rfile; + return lhs_elems.first.compare(rhs_elems.first); + } + + int const ret = lhs_elems.first.compare(rhs_elems.first); + if (ret != 0) return ret; + } + return 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 == nullptr || 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 + 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; + } + + 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 (aux::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(); + auto parts = lsplit_path(f); + if (parts.first == ".") f = parts.second; + 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..c04ec76 --- /dev/null +++ b/src/pe_crypto.cpp @@ -0,0 +1,413 @@ +/* + +Copyright (c) 2007, Un Shyam +Copyright (c) 2011, 2014-2019, Arvid Norberg +Copyright (c) 2016, 2018, Steven Siloti +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016-2018, 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. + +*/ + +#if !defined TORRENT_DISABLE_ENCRYPTION + +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#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(aux::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 + , aux::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..d433a07 --- /dev/null +++ b/src/peer_class.cpp @@ -0,0 +1,126 @@ +/* + +Copyright (c) 2014, 2017, 2019, Arvid Norberg +Copyright (c) 2018, 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/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..f3f8515 --- /dev/null +++ b/src/peer_class_set.cpp @@ -0,0 +1,73 @@ +/* + +Copyright (c) 2010, 2013-2014, 2016-2017, 2019, Arvid Norberg +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/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..85eeb21 --- /dev/null +++ b/src/peer_connection.cpp @@ -0,0 +1,6796 @@ +/* + +Copyright (c) 2016, tnextday +Copyright (c) 2003-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2016-2018, Steven Siloti +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2017-2018, Pavel Pimenov +Copyright (c) 2020, Viktor Elofsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/aux_/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/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/aux_/bandwidth_manager.hpp" +#include "libtorrent/request_blocks.hpp" // for request_a_block +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/aux_/alert_manager.hpp" // for alert_manager +#include "libtorrent/ip_filter.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/aux_/buffer.hpp" +#include "libtorrent/aux_/array.hpp" +#include "libtorrent/aux_/set_socket_buffer.hpp" +#include "libtorrent/aux_/set_traffic_class.hpp" + +#if TORRENT_USE_ASSERTS +#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& pack) + : peer_connection_hot_members(pack.tor, *pack.ses, *pack.sett) + , m_socket(std::move(pack.s)) + , m_peer_info(pack.peerinfo) + , m_counters(*pack.stats_counters) + , m_num_pieces(0) + , m_max_out_request_queue(aux::clamp_assign(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(make_work_guard(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 + + static_cast(socket_type_idx(m_socket))); + std::shared_ptr t = m_torrent.lock(); + + // the protocol_v2 flag should not be set for non-v2 torrents + TORRENT_ASSERT(!t || t->info_hash().has_v2() || !m_peer_info->protocol_v2); + + 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); + + 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() + , socket_type_name(m_socket) + , 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 + 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); + } + + 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 (m_settings.get_int(settings_pack::peer_tos) != 0) + { + int const tos = m_settings.get_int(settings_pack::peer_tos); + aux::set_traffic_class(m_socket, tos, ec); +#ifndef TORRENT_DISABLE_LOGGING + if (ec && should_log(peer_log_alert::outgoing)) + { + peer_log(peer_log_alert::outgoing, "SET_TOS", "tos: %d e: %s" + , tos, ec.message().c_str()); + } +#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(), socket_type_idx(m_socket)); + +#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" + , (aux::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(aux::is_v6(m_remote)); + + if (t && t->alerts().should_post()) + { + t->alerts().emplace_alert( + t->get_handle(), remote(), pid(), socket_type_idx(m_socket), peer_connect_alert::direction_t::out); + } +#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(); + post(m_ios, [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(associated_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(aux::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); + TORRENT_ASSERT(is_seed()); + + 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; + } + + TORRENT_ASSERT(!is_seed()); + + // 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 + + static_cast(socket_type_idx(m_socket)), -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->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 SUPPRESSED" + , 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()); +#if TORRENT_USE_ASSERTS + 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()); +#endif + if (m_have_piece.empty()) return false; + 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.get(m_connect) > seconds(30) && m_download_rate_peak > 0) + { + rate = m_download_rate_peak; + } + else if (aux::time_now() - m_last_unchoked.get(m_connect) < 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); + } + + sha1_hash peer_connection::associated_info_hash() const + { + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + auto const& ih = t->info_hash(); + // if protocol_v2 is set on the peer, this better be a v2 torrent, + // otherwise something isn't right + TORRENT_ASSERT(ih.has_v2() || !peer_info_struct()->protocol_v2); + return ih.get((ih.has_v2() && peer_info_struct()->protocol_v2) + ? protocol_version::V2 : protocol_version::V1); + } + + 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 (bytes_payload > 0) m_last_sent_payload.set(m_connect, clock_type::now()); + 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::validate_piece_request(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(info_hash_t 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(t->info_hash().get_best()).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.get_best()).c_str()); + } +#endif + +#ifndef TORRENT_DISABLE_DHT + ih.for_each([&](sha1_hash const& e, protocol_version) + { + if (dht::verify_secret_id(e)) + { + // 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 this peer supports v2, this better be a v2 torrent + TORRENT_ASSERT(t->info_hash().has_v2() || !(peer_info_struct() && peer_info_struct()->protocol_v2)); + + 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 = boost::get(&m_socket); + 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 (t && t->alerts().should_post()) + { + t->alerts().emplace_alert( + t->get_handle(), remote(), pid(), socket_type_idx(m_socket), peer_connect_alert::direction_t::in); + } + + if (t->info_hash().has_v2() && (t->info_hash().get(protocol_version::V2) == ih.v1 + || t->info_hash().v2 == ih.v2)) + { + peer_info_struct()->protocol_v2 = true; + TORRENT_ASSERT(t->info_hash().has_v2()); + } + + 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.set(m_connect, 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 + +#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_became_uninterested.set(m_connect, aux::time_now()); + 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(m_settings.get_int(settings_pack::max_piece_count))) + { + // 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(t->ready_for_connections()); + 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); + TORRENT_ASSERT(is_seed()); + +#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 const was_seed = is_seed(); + m_have_piece.clear_bit(index); + TORRENT_ASSERT(m_num_pieces > 0); + --m_num_pieces; + m_have_all = false; + + // 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); + TORRENT_ASSERT(!is_seed()); + } + } + + // ----------------------------- + // --------- 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()); + TORRENT_ASSERT(is_seed() == (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 const num_pieces = bits.count(); + t->set_seed(m_peer_info, num_pieces == m_have_piece.size()); + 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 + + m_have_piece.set_all(); + m_num_pieces = num_pieces; + t->peer_has_all(this); + TORRENT_ASSERT(is_seed()); + + 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 (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 (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); + if (m_num_invalid_requests < std::numeric_limits::max()) + ++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 super-seeded " + "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"); +#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())); +#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()); + } +#endif + + write_reject_request(r); + if (m_num_invalid_requests < std::numeric_limits::max()) + ++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"); +#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.get(m_connect) + && 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()); + TORRENT_ASSERT(r.length <= default_block_size); + TORRENT_ASSERT(r.length > 0); + + 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.set(m_connect, 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.set(m_connect, aux::time_now()); + TORRENT_ASSERT_VAL(m_outstanding_bytes >= bytes, 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 (!validate_piece_request(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 (aux::is_v4(m_remote) + && (m_remote.address().to_v4().to_uint() & 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(validate_piece_request(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.get(m_connect)))); +#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.set(m_connect, 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.get(m_connect)) < 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; + + bool const exceeded = m_disk_thread.async_write(t->storage(), p, data, self() + , [conn = self(), p, t] (storage_error const& e) + { conn->wrap(&peer_connection::on_disk_write_complete, e, p, t); }); + m_ses.deferred_submit_jobs(); + + // 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 + && 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.get(m_connect)))); +#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.set(m_connect, 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()); + + // this is for a future per-block request feature +#if 0 + if (t->info_hashes().has_v2()) + { + t->picker().started_hash_job(p.piece); + m_disk_thread.async_hash2(t->storage(), p.piece, p.start, {} + , [conn = self(), p](piece_index_t, sha256_hash const& h, storage_error const& e) + { + conn->wrap(&peer_connection::on_hash2_complete, e, p, h); + }); + m_ses.deferred_submit_jobs(); + } +#endif + + 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 const d = t->picker().get_downloaders(piece); + if (d.size() == 1) + { + // only make predictions if all remaining + // blocks are requested from the same peer + torrent_peer* const 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); + } + m_ses.deferred_submit_jobs(); + } + 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); + + 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_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_all = false; + + m_have_piece.clear_all(); + m_num_pieces = 0; + + TORRENT_ASSERT(!is_seed()); + + // 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() < int(m_queued_time_critical)) return false; + pending_block b = *rit; + m_request_queue.erase(rit); + m_request_queue.insert(m_request_queue.begin() + int(m_queued_time_critical), b); + + if (m_queued_time_critical < std::numeric_limits::max()) + ++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.set(m_connect, 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); + 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.set(m_connect, 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; + if (!m_interesting) + { + 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; + if (m_interesting) + { + m_interesting = false; + m_became_uninteresting.set(m_connect, aux::time_now()); + m_counters.inc_stats_counter(counters::num_peers_down_interested, -1); + } + + m_slow_start = false; + + disconnect_if_redundant(); + if (m_disconnecting) return; + + write_not_interested(); + +#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) + 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(validate_piece_request(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(validate_piece_request(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(validate_piece_request(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.set(m_connect, 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.set(m_connect, 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.set(m_connect, 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 + post(m_ses.get_context(), [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; + + set_close_reason(m_socket, error_to_close_reason(ec)); + close_reason_t const close_reason = get_close_reason(m_socket); +#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, socket_type_idx(m_socket), 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, self()); + } + + 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 aux::is_local(m_remote.address()) + || m_remote.address().is_loopback(); + } + + 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.get(m_connect) - 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.get(m_connect); + p.last_active = now - std::max(m_last_sent.get(m_connect), m_last_receive.get(m_connect)); + + // this will set the flags so that we can update them later + p.flags = {}; + get_specific_peer_info(p); + + if (m_snubbed) p.flags |= peer_info::snubbed; + if (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; + if (pi->seed) p.flags |= peer_info::seed; + } + else + { + if (is_seed()) p.flags |= peer_info::seed; + 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()); + } + + 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 = aux::clamp_assign(s); + } + + int peer_connection::max_out_request_queue() const + { + return int(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 = 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" + , int(m_desired_queue_size), int(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.get(m_connect)) + { + // 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.set(m_connect, 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.get(m_connect) + 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.get(m_connect); + + 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 the bw_network flag isn't set, it means we are not even trying to + // read from this peer's socket. Most likely because we're applying a + // rate limit. If the peer is "slow" because we are rate limiting it, + // don't enforce timeouts. However, as soon as we *do* read from the + // socket, we expect to receive data, and not have timed out. Then we + // can enforce the timeouts. + bool const reading_socket = 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 (reading_socket && 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 (reading_socket + && !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.get(m_connect) + , m_last_incoming_request.get(m_connect)) + , m_last_sent_payload.get(m_connect)); + + if (reading_socket + && !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.get(m_connect); + time_duration const d2 = now - m_became_uninteresting.get(m_connect); + time_duration const time_limit = seconds( + m_settings.get_int(settings_pack::inactivity_timeout)); + + // if we are close enough to the limit, consider the peer connection + // list full. This will enable the inactive timeout + bool const max_session_conns = m_ses.num_connections() + >= m_settings.get_int(settings_pack::connections_limit) - 5; + bool const max_torrent_conns = t && t->num_peers() + >= t->max_connections() - 5; + + // 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 (reading_socket + && !m_interesting + && !m_peer_interested + && d1 > time_limit + && d2 > time_limit + && (max_session_conns || max_torrent_conns) + && 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 (reading_socket + && !m_download_queue.empty() + && m_quota[download_channel] > 0 + && now > m_requested.get(m_connect) + 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.get(m_connect) > 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.get(m_connect))) + , 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 + + if (t->is_deleted()) + { + // can this happen here? +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "TORRENT_ABORTED", ""); +#endif + for (peer_request const& r : m_requests) + write_reject_request(r); + m_requests.clear(); + return; + } + + // 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 const& 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); + TORRENT_ASSERT(r.start >= 0); + + 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) + disk_job_flags_t flags; + if (t->info_hash().has_v1()) + flags |= disk_interface::v1_hash; + aux::vector hashes; + if (t->info_hash().has_v2()) + hashes.resize(t->torrent_file().orig_files().blocks_in_piece2(r.piece)); + + span v2_hashes(hashes); + m_disk_thread.async_hash(t->storage(), r.piece, v2_hashes, flags + , [conn = self(), h2 = std::move(hashes)] + (piece_index_t p, sha1_hash const& ph, storage_error const& e) + { conn->wrap(&peer_connection::on_seed_mode_hashed, p, ph, h2, 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::info, "PIECE_FAILED" + , "piece: %d s: %x l: %x piece failed 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()); + + m_disk_thread.async_read(t->storage(), r + , [conn = self(), r](disk_buffer_holder buf, storage_error const& ec) + { conn->wrap(&peer_connection::on_disk_read_complete, std::move(buf), ec, r, clock_type::now()); }); + } + m_last_sent_payload.set(m_connect, 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; + } + m_ses.deferred_submit_jobs(); + +#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, aux::vector const& block_hashes + , 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; + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-braces" +#endif + aux::array + hash_failed{ { boost::indeterminate, boost::indeterminate } }; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + // we're using the piece hashes here, we need the torrent to be loaded + if (!m_settings.get_bool(settings_pack::disable_hash_checks) + && t->info_hash().has_v1()) + { + hash_failed[protocol_version::V1] = piece_hash != t->torrent_file().hash_for_piece(piece); + } + + if (!m_settings.get_bool(settings_pack::disable_hash_checks) + && t->info_hash().has_v2()) + { + hash_failed[protocol_version::V2] = false; + + int const blocks_in_piece = t->torrent_file().files().blocks_in_piece2(piece); + + TORRENT_ASSERT(blocks_in_piece == int(block_hashes.size())); + + t->need_hash_picker(); + auto picker = t->get_hash_picker(); + set_block_hash_result result = set_block_hash_result::unknown(); + for (int i = 0; i < blocks_in_piece; ++i) + { + result = picker.set_block_hash(piece, i * default_block_size, block_hashes[i]); + if (result.status == set_block_hash_result::result::block_hash_failed + || result.status == set_block_hash_result::result::piece_hash_failed) + { + hash_failed[protocol_version::V2] = true; + } + } + + // if the last block still couldn't be verified + // it means we don't know the piece's root hash + // we must leave seed mode + if (result.status == set_block_hash_result::result::unknown) + hash_failed[protocol_version::V1] = hash_failed[protocol_version::V2] = true; + } + + if ((hash_failed[protocol_version::V1] && !hash_failed[protocol_version::V2]) + || (!hash_failed[protocol_version::V1] && hash_failed[protocol_version::V2])) + { + t->set_error(errors::torrent_inconsistent_hashes, torrent_status::error_file_none); + t->pause(); + return; + } + + if (hash_failed[protocol_version::V1] || hash_failed[protocol_version::V2]) + { +#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(); + } + + // this is for a future per-block request feature +#if 0 + void peer_connection::on_hash2_complete(storage_error const& error + , peer_request const& r, sha256_hash const& hash) + { + auto t = associated_torrent().lock(); + if (!t) return; + + t->picker().completed_hash_job(r.piece); + + t->need_hash_picker(); + auto result = t->get_hash_picker().set_block_hash(r.piece, r.start, hash); + + switch (result.status) + { + case set_block_hash_result::block_hash_failed: + // If the hash failed immediately at the leaf layer it means that + // the chuck hash is known so this peer definately sent bad data. + t->piece_failed(r.piece, std::vector{r.start / default_block_size}); + TORRENT_ASSERT(m_disconnecting); + return; + case set_block_hash_result::piece_hash_failed: + t->verify_block_hashes(r.piece); + break; + case set_block_hash_result::success: + { + t->need_picker(); + int const blocks_per_piece = t->torrent_file().files().piece_length() / default_block_size; + for (piece_index_t verified_piece = int(r.piece) + result.first_verified_block / blocks_per_piece + , end = int(verified_piece) + (result.num_verified + blocks_per_piece - 1) / blocks_per_piece + ; verified_piece < end; ++verified_piece) + { + if (!t->picker().is_piece_finished(verified_piece) + || !t->get_hash_picker().piece_verified(verified_piece) + || t->picker().is_hashing(verified_piece)) + continue; + t->piece_passed(verified_piece); + } + break; + } + case set_block_hash_result::unknown:break; + default: + TORRENT_ASSERT_FAIL(); + break; + } + } +#endif + + void peer_connection::on_disk_read_complete(disk_buffer_holder buffer + , storage_error const& error + , peer_request const& r, time_point const issue_time) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(r.length >= 0); + + // 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 e: %s rtt: %d us" + , static_cast(r.piece), r.start, r.length + , static_cast(buffer.data()) + , 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) + { + 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, aux::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)); + + aux::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 + + using write_handler_type = aux::handler< + peer_connection + , decltype(&peer_connection::on_send_data) + , &peer_connection::on_send_data + , &peer_connection::on_error + , &peer_connection::on_exception + , decltype(m_write_handler_storage) + , &peer_connection::m_write_handler_storage + >; + static_assert(sizeof(write_handler_type) == sizeof(std::shared_ptr) + , "write handler does not have the expected size"); + m_socket.async_write_some(vec, write_handler_type(self())); + + m_channel_state[upload_channel] |= peer_info::bw_network; + m_last_sent.set(m_connect, 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"); + + using read_handler_type = aux::handler< + peer_connection + , decltype(&peer_connection::on_receive_data) + , &peer_connection::on_receive_data + , &peer_connection::on_error + , &peer_connection::on_exception + , decltype(m_read_handler_storage) + , &peer_connection::m_read_handler_storage + >; + static_assert(sizeof(read_handler_type) == sizeof(std::shared_ptr) + , "read handler does not have the expected size"); + m_socket.async_read_some(boost::asio::buffer(vec.data(), std::size_t(vec.size())), read_handler_type(self())); + } + + 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' + aux::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, aux::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(error_code const& 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.set(m_connect, 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_buffer(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.set(m_connect, 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(aux::is_v6(m_remote)); + +#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 (m_settings.get_int(settings_pack::peer_tos) != 0) + { + int const tos = m_settings.get_int(settings_pack::peer_tos); + aux::set_traffic_class(m_socket, tos, ec); +#ifndef TORRENT_DISABLE_LOGGING + if (ec && should_log(peer_log_alert::outgoing)) + { + peer_log(peer_log_alert::outgoing, "SET_TOS", "tos: %d e: %s" + , tos, ec.message().c_str()); + } +#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), aux::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.set(m_connect, 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); + } + + for (auto const& r : m_requests) + { + TORRENT_ASSERT(r.piece >= piece_index_t(0)); + TORRENT_ASSERT(r.piece < piece_index_t(m_have_piece.size())); + if (t) TORRENT_ASSERT(r.start + r.length <= t->torrent_file().piece_size(r.piece)); + TORRENT_ASSERT(r.length > 0); + TORRENT_ASSERT(r.length <= default_block_size); + TORRENT_ASSERT(r.start >= 0); + } + + 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; + } + + auto const& ih = t->info_hash(); + if (peer_info_struct() && peer_info_struct()->protocol_v2) + TORRENT_ASSERT(ih.has_v2()); + + 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() + && 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 (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(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.get(m_connect); + 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()); + m_upload_only = u; + disconnect_if_redundant(); + } + +} diff --git a/src/peer_connection_handle.cpp b/src/peer_connection_handle.cpp new file mode 100644 index 0000000..3d67165 --- /dev/null +++ b/src/peer_connection_handle.cpp @@ -0,0 +1,357 @@ +/* + +Copyright (c) 2015, 2017-2018, Steven Siloti +Copyright (c) 2015-2019, Arvid Norberg +Copyright (c) 2016-2017, 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/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..4cd58ed --- /dev/null +++ b/src/peer_info.cpp @@ -0,0 +1,89 @@ +/* + +Copyright (c) 2017-2018, 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/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::outgoing_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 + + constexpr connection_type_t peer_info::standard_bittorrent; + constexpr connection_type_t peer_info::web_seed; + constexpr connection_type_t peer_info::http_seed; +} diff --git a/src/peer_list.cpp b/src/peer_list.cpp new file mode 100644 index 0000000..b2d276f --- /dev/null +++ b/src/peer_list.cpp @@ -0,0 +1,1335 @@ +/* + +Copyright (c) 2003-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2009, Daniel Wallin +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2016, 2018, Steven Siloti +Copyright (c) 2016, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/invariant_check.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/piece_picker.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/aux_/ip_helpers.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; + }; + + // this returns true if lhs is a better erase candidate than rhs + bool compare_peer_erase(torrent_peer const& lhs, torrent_peer const& rhs) + { + 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 compare_peer(torrent_peer const* lhs, torrent_peer const* rhs + , external_ip const& external, int const external_port, bool const finished) + { + // 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 = aux::is_local(lhs->address()); + bool const rhs_local = aux::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; + + if (finished && lhs->maybe_upload_only != rhs->maybe_upload_only) + { + // if we're finished, de-prioritze peers we think may be seeds + // since being upload-only doesn't necessarily mean it's a good peer + // to be connected to as a downloader, we don't prioritize the + // inverse when we're not finished + return rhs->maybe_upload_only; + } + + 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; + } + +} // anonymous namespace + +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 (auto 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 (auto 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 + auto const 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; + // now that we're connected, no need to assume ther peer is a seed + // anymore. We'll soon know. + p->maybe_upload_only = false; + 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, m_finished)) 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(&compare_peer, _1, _2, std::cref(external), external_port, bool(m_finished))); + + 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; + + 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 disconnect1 = ((our_port < other_port) && !outgoing1) + || ((our_port > other_port) && outgoing1) + || ((our_port == other_port) && random(1)); + disconnect1 &= !i->connection->failed(); + +#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::aux::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 (bool(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->maybe_upload_only = true; + if (flags & pex_utp) + p->supports_utp = true; + if (flags & pex_holepunch) + p->supports_holepunch = true; + if (flags & pex_lt_v2) + p->protocol_v2 = 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) + p->maybe_upload_only = true; + if (flags & pex_utp) + p->supports_utp = true; + if (flags & pex_holepunch) + p->supports_holepunch = true; + if (flags & pex_lt_v2) + p->protocol_v2 = 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; + + auto 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); + + // if we're finished, p->seed must be 0. We shouldn't be connecting to + // seeds in that case + TORRENT_ASSERT(m_finished == 0 || p->seed == 0); + + 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); + + auto const 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 + +} diff --git a/src/performance_counters.cpp b/src/performance_counters.cpp new file mode 100644 index 0000000..323233b --- /dev/null +++ b/src/performance_counters.cpp @@ -0,0 +1,160 @@ +/* + +Copyright (c) 2010, 2013-2020, Arvid Norberg +Copyright (c) 2016-2017, 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/performance_counters.hpp" +#include "libtorrent/assert.hpp" +#include // for memset + +namespace libtorrent { + + // TODO: move stats_counter_t out of counters + // TODO: should bittorrent keep-alive messages have a counter too? + // TODO: It would be nice if this could be an internal type. default_disk_constructor depends on it now + 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..88e21fd --- /dev/null +++ b/src/piece_picker.cpp @@ -0,0 +1,3933 @@ +/* + +Copyright (c) 2003-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2016, 2019, Steven Siloti +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2018, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/invariant_check.hpp" + +// this is really only useful for debugging unit tests +//#define TORRENT_PICKER_LOG + +using namespace std::placeholders; + +namespace { +template +bool contains(C const& c, V v) +{ + return std::find(c.begin(), c.end(), v) != c.end(); +} +} + +#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::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(std::int64_t const total_size, int const piece_size) + : m_priority_boundaries(1, m_pieces.end_index()) + { + TORRENT_ASSERT(total_size > 0); + TORRENT_ASSERT(piece_size > 0); +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "new piece_picker" << std::endl; +#endif +#if TORRENT_USE_INVARIANT_CHECKS + check_invariant(); +#endif + + resize(total_size, piece_size); + } + + void piece_picker::resize(std::int64_t const total_size, int const piece_size) + { + TORRENT_ASSERT(total_size > 0); + TORRENT_ASSERT(piece_size > 0); + + m_total_size = total_size; + m_piece_size = piece_size; + + int const blocks_in_piece + = (piece_size + block_size() - 1) / block_size(); + int const blocks_in_last_piece + = ((total_size % piece_size) + block_size() - 1) / block_size(); + int const num_pieces = int((total_size + piece_size - 1) / piece_size); + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "piece_picker::resize()" << std::endl; +#endif + + if (blocks_in_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(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_bytes = 0; + m_filtered_pad_bytes = 0; + m_have_filtered_pad_bytes = 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_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 <= 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 + int const blocks = blocks_per_piece(); + block_index = int(m_block_info.size()) / blocks; + TORRENT_ASSERT((m_block_info.size() % std::size_t(blocks)) == 0); + m_block_info.resize(m_block_info.size() + std::size_t(blocks)); + } + 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) * blocks_per_piece() + + blocks_per_piece() <= int(m_block_info.size())); + + // the number of non-pad blocks in this piece. Any blocks past this will + // be assumed we have already + + int const payload_blocks = blocks_per_piece() - pad_bytes_in_piece(piece) / block_size(); + + int block_idx = 0; + for (auto& info : mutable_blocks_for_piece(ret)) + { + info.num_peers = 0; + info.state = block_info::state_none; + if (block_idx >= payload_blocks) + { + 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) * blocks_per_piece(); + TORRENT_ASSERT(idx + 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) * blocks_per_piece() + + 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(span 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); + } + } + + 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_bytes <= num_pad_bytes()); + TORRENT_ASSERT(m_have_pad_bytes >= 0); + TORRENT_ASSERT(m_filtered_pad_bytes <= num_pad_bytes()); + TORRENT_ASSERT(m_filtered_pad_bytes >= 0); + TORRENT_ASSERT(m_have_filtered_pad_bytes <= num_pad_bytes()); + TORRENT_ASSERT(m_have_filtered_pad_bytes >= 0); + TORRENT_ASSERT(m_have_filtered_pad_bytes + m_filtered_pad_bytes <= num_pad_bytes()); + TORRENT_ASSERT(m_have_filtered_pad_bytes <= m_have_pad_bytes); + + // 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_bytes = 0; + int num_filtered_pad_bytes = 0; + int num_have_filtered_pad_bytes = 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_bytes += pad_bytes_in_piece(piece); + } + else + { + ++num_have_filtered; + num_have_filtered_pad_bytes += pad_bytes_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_bytes += pad_bytes_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_bytes == m_have_pad_bytes); + TORRENT_ASSERT(num_filtered_pad_bytes == m_filtered_pad_bytes); + TORRENT_ASSERT(num_have_filtered_pad_bytes == m_have_filtered_pad_bytes); + + 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, span const blocks) + { + 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(); + // if the piece was cancelled, it may have been removed + 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) * blocks_per_piece() + + 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); + + // if we know which blocks failed, just restore those + if (!blocks.empty()) + { + auto const binfo = mutable_blocks_for_piece(*i); + for (int const block : blocks) + { + block_info& info = binfo[block]; + TORRENT_ASSERT(info.state >= block_info::state_writing); + if (info.state == block_info::state_writing) + { + TORRENT_ASSERT(i->writing > 0); + --i->writing; + } + else if (info.state == block_info::state_finished) + { + TORRENT_ASSERT(i->finished > 0); + --i->finished; + } + else if (info.state == block_info::state_requested) + { + // this is not expected to happen, but if it does, leave the + // block state intact + continue; + } + info.peer = nullptr; + info.state = block_info::state_none; + } + i = update_piece_state(i); + } + + if (blocks.empty() || i->requested + i->finished + i->writing == 0) + { + 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_bytes += pad_bytes_in_piece(index); + ++m_num_filtered; + + TORRENT_ASSERT(m_have_filtered_pad_bytes >= pad_bytes_in_piece(index)); + m_have_filtered_pad_bytes -= pad_bytes_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_bytes -= pad_bytes_in_piece(index); + TORRENT_ASSERT(m_have_pad_bytes >= 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()); + TORRENT_ASSERT(i->hashing == 0); + // 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_bytes >= pad_bytes_in_piece(index)); + m_filtered_pad_bytes -= pad_bytes_in_piece(index); + TORRENT_ASSERT(m_num_filtered > 0); + --m_num_filtered; + + m_have_filtered_pad_bytes += pad_bytes_in_piece(index); + ++m_num_have_filtered; + } + ++m_num_have; + ++m_num_passed; + m_have_pad_bytes += pad_bytes_in_piece(index); + TORRENT_ASSERT(m_have_pad_bytes <= num_pad_bytes()); + 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_bytes += m_filtered_pad_bytes; + m_filtered_pad_bytes = 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_bytes += pad_bytes_in_piece(index); + ++m_num_have_filtered; + } + else + { + m_filtered_pad_bytes += pad_bytes_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_bytes >= pad_bytes_in_piece(index)); + m_have_filtered_pad_bytes -= pad_bytes_in_piece(index); + TORRENT_ASSERT(m_num_have_filtered > 0); + --m_num_have_filtered; + } + else + { + TORRENT_ASSERT(m_filtered_pad_bytes >= pad_bytes_in_piece(index)); + m_filtered_pad_bytes -= pad_bytes_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{static_cast(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 = lhs->finished + lhs->writing + lhs->requested; + int rhs_blocks = rhs->finished + rhs->writing + rhs->requested; + return lhs_blocks > rhs_blocks; + } + + // 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 * 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; + std::vector ignored_pieces; + + // 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); + + 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) + { + 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, ignored_pieces + , options); + // we want to ignore this piece from now on, since we've already + // picked from it + ignored_pieces.push_back(i); + 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, ignored_pieces + , options); + if (num_blocks <= 0) return ret; + } + + 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, ignored_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, ignored_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) + { + 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, ignored_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, ignored_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); + + 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, ignored_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) || contains(ignored_pieces, piece)) + { + 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); + + auto const range = expand_piece(piece + , prefer_contiguous_blocks, pieces, options); + for (piece_index_t const k : range) + { + if (contains(ignored_pieces, k)) + { + --prefer_contiguous_blocks; + if (prefer_contiguous_blocks == 0 + && num_blocks <= 0) break; + continue; + } + ignored_pieces.push_back(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 = *range.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, ignored_pieces + , 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; + + // 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); + + 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; + + 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 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& ignore + , picker_options_t const options) const + { + TORRENT_ASSERT(is_piece_free(piece, pieces)); + + // ignore pieces found in the ignore list + if (contains(ignore, piece)) 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); + } + + // pick a new piece + int payload_blocks = blocks_in_piece(piece) - pad_bytes_in_piece(piece) / block_size(); + + if (prefer_contiguous_blocks == 0) + { + if (payload_blocks > num_blocks) + payload_blocks = num_blocks; + TORRENT_ASSERT(is_piece_free(piece, pieces)); + for (int j = 0; j < payload_blocks; ++j) + { + TORRENT_ASSERT(j < blocks_in_piece(piece)); + interesting_blocks.emplace_back(piece, j); + } + num_blocks -= payload_blocks; + } + else + { + auto const range = expand_piece(piece, prefer_contiguous_blocks + , pieces, options); + for (piece_index_t const k : range) + { + if (contains(ignore, k)) + { + --prefer_contiguous_blocks; + if (prefer_contiguous_blocks == 0 + && num_blocks <= 0) break; + continue; + } + ignore.push_back(k); + + TORRENT_ASSERT(m_piece_map[k].priority(this) > 0); + payload_blocks = blocks_in_piece(k) - pad_bytes_in_piece(k) / block_size(); + TORRENT_ASSERT(is_piece_free(k, pieces)); + for (int j = 0; j < payload_blocks; ++j) + { + TORRENT_ASSERT(j < blocks_in_piece(k)); + 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; + } + + index_range + 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 {piece, next(piece)}; + + // round to even pieces and expand in order to get the number of + // contiguous pieces we want + int const blocks = blocks_per_piece(); + int const whole_pieces = (contiguous_blocks + blocks - 1) / blocks; + + 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 {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) <= 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::is_hashing(piece_index_t const piece) const + { + piece_pos const& p = m_piece_map[piece]; + auto const state = p.download_queue(); + if (state == piece_pos::piece_open) + return false; + auto const i = find_dl_piece(state, piece); + TORRENT_ASSERT(i != m_downloads[state].end()); + return i->hashing > 0; + } + + 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 + dp->hashing == 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 / 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 / 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 (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 (contains(m_recent_extents, this_extent)) + 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; + } + + void piece_picker::started_hash_job(piece_index_t piece) + { + TORRENT_ASSERT(piece != piece_block::invalid.piece_index); + TORRENT_ASSERT(piece < m_piece_map.end_index()); + + piece_pos& p = m_piece_map[piece]; + TORRENT_ASSERT(p.downloading()); + if (p.downloading()) + { + auto i = find_dl_piece(p.download_queue(), piece); + TORRENT_ASSERT(i != m_downloads[p.download_queue()].end()); +#if 1 + TORRENT_ASSERT(i->hashing == 0); + i->hashing = 1; + + // this is for a future per-block request feature +#else + ++i->hashing; +#endif + } + } + + void piece_picker::completed_hash_job(piece_index_t piece) + { + TORRENT_ASSERT(piece != piece_block::invalid.piece_index); + TORRENT_ASSERT(piece < m_piece_map.end_index()); + + piece_pos& p = m_piece_map[piece]; + // if this piece was cancelled/aborted while hashing, the piece has been + // removed from the list + if (!p.downloading()) return; + auto i = find_dl_piece(p.download_queue(), piece); + TORRENT_ASSERT(i != m_downloads[p.download_queue()].end()); + +#if 1 + TORRENT_ASSERT(i->hashing == 1); + i->hashing = 0; + + // this is for a future per-block request feature +#else + TORRENT_ASSERT(i->hashing > 0); + --i->hashing; +#endif + } + + // 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 failed 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 + i->hashing == 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 + i->hashing == 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 && i->hashing == 0) + we_have(i->index); + } + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + } + + void piece_picker::set_pad_bytes(piece_index_t const piece, int const bytes) + { + TORRENT_ASSERT(bytes <= piece_size(piece)); + TORRENT_ASSERT(m_pads_in_piece.find(piece) == m_pads_in_piece.end()); + // it doesn't make sense to call this with 0. All pieces have 0 pad + // bytes implicitly + TORRENT_ASSERT(bytes > 0); + TORRENT_ASSERT(bytes <= piece_size(piece)); + + m_num_pad_bytes += bytes; + m_pads_in_piece[piece] = bytes; + + piece_pos& p = m_piece_map[piece]; + if (p.have()) + { + m_have_pad_bytes += bytes; + if (p.filtered()) + m_have_filtered_pad_bytes += bytes; + } + else if (p.filtered()) + { + m_filtered_pad_bytes += bytes; + } + + // if we mark and entire piece as a pad file, we need to also + // consder that piece as "had" and increment some counters + if (piece_size(piece) == bytes) + { + // the entire piece is a pad file + we_have(piece); + } + } + + int piece_picker::pad_bytes_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; + } + + int piece_picker::piece_size(piece_index_t const p) const + { + int const pieces = num_pieces(); + if (static_cast(p) != pieces - 1) + return m_piece_size; + + std::int64_t const size_except_last + = (pieces - 1) * std::int64_t(m_piece_size); + std::int64_t const size = m_total_size - size_except_last; + return int(size); + } + + int piece_picker::blocks_per_piece() const + { + int const blk_size = block_size(); + int ret = (m_piece_size + blk_size - 1) / blk_size; + TORRENT_ASSERT(ret > 0); + return ret; + } + + std::vector piece_picker::get_downloaders(piece_index_t const index) const + { + std::vector d; + 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) + { + d.resize(std::size_t(num_blocks), nullptr); + return d; + } + + 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); + } + return d; + } + + 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 + i->hashing == 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_bytes() - m_filtered_pad_bytes - m_have_filtered_pad_bytes + , want_last }; + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.last_piece == true)); + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.pad_bytes > 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_bytes - m_have_filtered_pad_bytes + , have_last && want_last }; + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.last_piece == true)); + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.pad_bytes > 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_bytes + , have_last }; + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.last_piece == true)); + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.pad_bytes > 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_bytes() + , true}; + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.last_piece == true)); + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.pad_bytes > 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..505c9c9 --- /dev/null +++ b/src/platform_util.cpp @@ -0,0 +1,132 @@ +/* + +Copyright (c) 2010, 2014-2018, 2020, Arvid Norberg +Copyright (c) 2018, Alden Torres +Copyright (c) 2020, zywo +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 + +#if TORRENT_HAS_PTHREAD_SET_NAME +#include +#ifdef TORRENT_BSD +#include +#endif +#endif + +#ifdef TORRENT_BEOS +#include +#endif + +#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" +#include "libtorrent/aux_/win_util.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 + + int const inf = 10000000; + + struct rlimit rl{}; + if (getrlimit(RLIMIT_NOFILE, &rl) == 0) + { + if (rl.rlim_cur == rlim_infinity) return inf; + return rl.rlim_cur <= static_cast(inf) + ? static_cast(rl.rlim_cur) : inf; + } + 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 + } + + void set_thread_name(char const* name) + { + TORRENT_UNUSED(name); +#if TORRENT_HAS_PTHREAD_SET_NAME +#ifdef TORRENT_BSD + pthread_set_name_np(pthread_self(), name); +#else + pthread_setname_np(pthread_self(), name); +#endif +#endif +#ifdef TORRENT_WINDOWS + using SetThreadDescription_t = HRESULT (WINAPI*)(HANDLE, PCWSTR); + auto SetThreadDescription = + aux::get_library_procedure("SetThreadDescription"); + if (SetThreadDescription) { + + wchar_t wide_name[50]; + int i = -1; + do { + ++i; + wide_name[i] = name[i]; + } while (name[i] != 0); + SetThreadDescription(GetCurrentThread(), wide_name); + } +#endif +#ifdef TORRENT_BEOS + rename_thread(find_thread(nullptr), name); +#endif + } +} diff --git a/src/posix_disk_io.cpp b/src/posix_disk_io.cpp new file mode 100644 index 0000000..b5e5dbf --- /dev/null +++ b/src/posix_disk_io.cpp @@ -0,0 +1,406 @@ +/* + +Copyright (c) 2016, 2019-2020, Arvid Norberg +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2020, 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/posix_disk_io.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/aux_/disk_buffer_pool.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/aux_/posix_storage.hpp" +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/aux_/storage_free_list.hpp" + +#include + +namespace libtorrent { + +namespace { + + using aux::posix_storage; + +} // anonymous namespace + + struct TORRENT_EXTRA_EXPORT posix_disk_io final + : disk_interface + { + posix_disk_io(io_context& ios, settings_interface const& sett, counters& cnt) + : m_settings(sett) + , m_buffer_pool(ios) + , m_stats_counters(cnt) + , m_ios(ios) + { + settings_updated(); + } + + void settings_updated() override + { + m_buffer_pool.set_settings(m_settings); + } + + storage_holder new_torrent(storage_params const& params + , std::shared_ptr const&) override + { + // make sure we can remove this torrent without causing a memory + // allocation, by causing the allocation now instead + storage_index_t const idx = m_free_slots.new_index(m_torrents.end_index()); + auto storage = std::make_unique(params); + if (idx == m_torrents.end_index()) m_torrents.emplace_back(std::move(storage)); + else m_torrents[idx] = std::move(storage); + return storage_holder(idx, *this); + } + + void remove_torrent(storage_index_t const idx) override + { + m_torrents[idx].reset(); + m_free_slots.add(idx); + } + + void abort(bool) override {} + + void async_read(storage_index_t storage, peer_request const& r + , std::function handler + , disk_job_flags_t) override + { + disk_buffer_holder buffer = disk_buffer_holder(m_buffer_pool, m_buffer_pool.allocate_buffer("send buffer"), default_block_size); + storage_error error; + if (!buffer) + { + error.ec = errors::no_memory; + error.operation = operation_t::alloc_cache_piece; + post(m_ios, [=, h = std::move(handler)]{ h(disk_buffer_holder(m_buffer_pool, nullptr, 0), error); }); + return; + } + + time_point const start_time = clock_type::now(); + + iovec_t buf = {buffer.data(), r.length}; + + m_torrents[storage]->readv(m_settings, buf, r.piece, r.start, error); + + if (!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); + } + + post(m_ios, [h = std::move(handler), b = std::move(buffer), error] () mutable + { h(std::move(b), error); }); + } + + bool async_write(storage_index_t storage, peer_request const& r + , char const* buf, std::shared_ptr + , std::function handler + , disk_job_flags_t) override + { + // TODO: 3 this const_cast can be removed once iovec_t is no longer a + // thing, but we just use plain spans + iovec_t const b = { const_cast(buf), r.length }; + + time_point const start_time = clock_type::now(); + + storage_error error; + m_torrents[storage]->writev(m_settings, b, r.piece, r.start, error); + + if (!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); + } + + post(m_ios, [=, h = std::move(handler)]{ h(error); }); + return false; + } + + void async_hash(storage_index_t storage, piece_index_t const piece + , span block_hashes, disk_job_flags_t flags + , std::function handler) override + { + time_point const start_time = clock_type::now(); + + bool const v1 = bool(flags & disk_interface::v1_hash); + bool const v2 = !block_hashes.empty(); + + disk_buffer_holder buffer = disk_buffer_holder(m_buffer_pool, m_buffer_pool.allocate_buffer("hash buffer"), default_block_size); + storage_error error; + if (!buffer) + { + error.ec = errors::no_memory; + error.operation = operation_t::alloc_cache_piece; + post(m_ios, [=, h = std::move(handler)]{ h(piece, sha1_hash{}, error); }); + return; + } + hasher ph; + + posix_storage* st = m_torrents[storage].get(); + + int const piece_size = v1 ? st->files().piece_size(piece) : 0; + int const piece_size2 = v2 ? st->orig_files().piece_size2(piece) : 0; + int const blocks_in_piece = v1 ? (piece_size + default_block_size - 1) / default_block_size : 0; + int const blocks_in_piece2 = v2 ? st->orig_files().blocks_in_piece2(piece) : 0; + + TORRENT_ASSERT(!v2 || int(block_hashes.size()) >= blocks_in_piece2); + + int offset = 0; + int const blocks_to_read = std::max(blocks_in_piece, blocks_in_piece2); + for (int i = 0; i < blocks_to_read; ++i) + { + bool const v2_block = i < blocks_in_piece2; + + auto const len = v1 ? std::min(default_block_size, piece_size - offset) : 0; + auto const len2 = v2_block ? std::min(default_block_size, piece_size2 - offset) : 0; + + iovec_t b = {buffer.data(), std::max(len, len2)}; + int const ret = st->readv(m_settings, b, piece, offset, error); + offset += default_block_size; + if (ret <= 0) break; + if (v1) + ph.update(b.first(std::min(ret, len))); + if (v2_block) + block_hashes[i] = hasher256(b.first(std::min(ret, len2))).final(); + } + + sha1_hash const hash = v1 ? ph.final() : sha1_hash(); + + if (!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, blocks_to_read); + m_stats_counters.inc_stats_counter(counters::num_read_ops); + m_stats_counters.inc_stats_counter(counters::disk_hash_time, read_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, read_time); + } + + post(m_ios, [=, h = std::move(handler)]{ h(piece, hash, error); }); + } + + void async_hash2(storage_index_t storage, piece_index_t const piece, int offset, disk_job_flags_t + , std::function handler) override + { + time_point const start_time = clock_type::now(); + + disk_buffer_holder buffer = disk_buffer_holder(m_buffer_pool, m_buffer_pool.allocate_buffer("hash buffer"), 0x4000); + storage_error error; + if (!buffer) + { + error.ec = errors::no_memory; + error.operation = operation_t::alloc_cache_piece; + post(m_ios, [=, h = std::move(handler)]{ h(piece, sha256_hash{}, error); }); + return; + } + + posix_storage* st = m_torrents[storage].get(); + + int const piece_size = st->files().piece_size2(piece); + + std::ptrdiff_t const len = std::min(default_block_size, piece_size - offset); + + hasher256 ph; + iovec_t b = {buffer.data(), len}; + int const ret = st->readv(m_settings, b, piece, offset, error); + if (ret > 0) + ph.update(b.first(ret)); + + sha256_hash const hash = ph.final(); + + if (!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_hash_time, read_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, read_time); + } + + post(m_ios, [=, h = std::move(handler)]{ h(piece, hash, error); }); + } + + + void async_move_storage(storage_index_t const storage, std::string p + , move_flags_t const flags + , std::function handler) override + { + posix_storage* st = m_torrents[storage].get(); + storage_error ec; + status_t ret; + std::tie(ret, p) = st->move_storage(p, flags, ec); + post(m_ios, [=, h = std::move(handler)]{ h(ret, p, ec); }); + } + + void async_release_files(storage_index_t storage, std::function handler) override + { + posix_storage* st = m_torrents[storage].get(); + st->release_files(); + if (!handler) return; + post(m_ios, [=]{ handler(); }); + } + + void async_delete_files(storage_index_t storage, remove_flags_t const options + , std::function handler) override + { + storage_error error; + posix_storage* st = m_torrents[storage].get(); + st->delete_files(options, error); + post(m_ios, [=, h = std::move(handler)]{ h(error); }); + } + + void async_check_files(storage_index_t storage + , add_torrent_params const* resume_data + , aux::vector links + , std::function handler) override + { + posix_storage* st = m_torrents[storage].get(); + + add_torrent_params tmp; + add_torrent_params const* rd = resume_data ? resume_data : &tmp; + + storage_error error; + status_t const ret = [&] + { + auto const ret_flag = st->initialize(m_settings, error); + if (error) return status_t::fatal_disk_error | ret_flag; + + bool const verify_success = st->verify_resume_data(*rd + , std::move(links), error); + + if (m_settings.get_bool(settings_pack::no_recheck_incomplete_resume)) + return status_t::no_error | ret_flag; + + 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 ((st->has_any_file(ignore)) + ? status_t::need_full_check + : status_t::no_error) + | ret_flag; + } + + return (verify_success + ? status_t::no_error + : status_t::need_full_check) + | ret_flag; + }(); + + post(m_ios, [error, ret, h = std::move(handler)]{ h(ret, error); }); + } + + void async_rename_file(storage_index_t const storage + , file_index_t const idx + , std::string name + , std::function handler) override + { + posix_storage* st = m_torrents[storage].get(); + storage_error error; + st->rename_file(idx, name, error); + post(m_ios, [idx, error, h = std::move(handler), n = std::move(name)] () mutable + { h(std::move(n), idx, error); }); + } + + void async_stop_torrent(storage_index_t, std::function handler) override + { + if (!handler) return; + post(m_ios, std::move(handler)); + } + + void async_set_file_priority(storage_index_t const storage + , aux::vector prio + , std::function)> handler) override + { + posix_storage* st = m_torrents[storage].get(); + storage_error error; + st->set_file_priority(prio, error); + post(m_ios, [p = std::move(prio), h = std::move(handler), error] () mutable + { h(error, std::move(p)); }); + } + + void async_clear_piece(storage_index_t, piece_index_t index + , std::function handler) override + { + post(m_ios, [=, h = std::move(handler)]{ h(index); }); + } + + void update_stats_counters(counters&) const override {} + + std::vector get_status(storage_index_t) const override + { return {}; } + + void submit_jobs() override {} + + private: + + aux::vector, storage_index_t> m_torrents; + + // slots that are unused in the m_torrents vector + aux::storage_free_list m_free_slots; + + settings_interface const& m_settings; + + // disk cache + aux::disk_buffer_pool m_buffer_pool; + + counters& m_stats_counters; + + // callbacks are posted on this + io_context& m_ios; + }; + + TORRENT_EXPORT std::unique_ptr posix_disk_io_constructor( + io_context& ios, settings_interface const& sett, counters& cnt) + { + return std::make_unique(ios, sett, cnt); + } +} + diff --git a/src/posix_part_file.cpp b/src/posix_part_file.cpp new file mode 100644 index 0000000..466b538 --- /dev/null +++ b/src/posix_part_file.cpp @@ -0,0 +1,480 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016-2017, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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 posix_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 posix_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/aux_/posix_part_file.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t + +#include // for std::function +#include + +namespace { + + // round up to even kilobyte + int round_up(int n) + { return (n + 1023) & ~0x3ff; } +} + +namespace libtorrent { +namespace aux { + + posix_part_file::posix_part_file(std::string path, std::string name + , int const num_pieces, int const piece_size) + : m_path(std::move(path)) + , m_name(std::move(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)); + auto const n = std::fread(header.data(), 1, header.size(), f.file()); + if (std::size_t(n) != header.size()) + { + ec.assign(errno, generic_category()); + return; + } + + // we don't have a full header. consider the file empty + if (n < std::size_t(m_header_size)) return; + using namespace libtorrent::aux; + + 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); + } + } + + posix_part_file::~posix_part_file() + { + error_code ec; + flush_metadata_impl(ec); + } + + slot_index_t posix_part_file::allocate_slot(piece_index_t const piece) + { + 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 posix_part_file::writev(span bufs, piece_index_t const piece + , int const offset, error_code& ec) + { + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(int(bufs.size()) + offset <= m_piece_size); + + auto f = open_file(open_mode::read_write, 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; + + if (portable_fseeko(f.file(), slot_offset(slot) + offset, SEEK_SET) != 0) + { + ec.assign(errno, generic_category()); + return -1; + } + int ret = 0; + for (auto const& b : bufs) + { + auto const written = std::fwrite(b.data(), 1, std::size_t(b.size()), f.file()); + if (written != std::size_t(b.size())) + { + ec.assign(errno, generic_category()); + return -1; + } + ret += int(written); + } + return ret; + } + + int posix_part_file::readv(span bufs + , piece_index_t const piece + , int const offset, error_code& ec) + { + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(int(bufs.size()) + offset <= m_piece_size); + + 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; + + auto f = open_file(open_mode::read_only, ec); + if (ec) return -1; + + if (portable_fseeko(f.file(), slot_offset(slot) + offset, SEEK_SET) != 0) + { + ec.assign(errno, generic_category()); + return -1; + } + int ret = 0; + for (auto const& b : bufs) + { + auto const read = std::fread(b.data(), 1, std::size_t(b.size()), f.file()); + if (read != std::size_t(b.size())) + { + ec.assign(errno, generic_category()); + return -1; + } + ret += int(read); + } + return ret; + } + + int posix_part_file::hashv(hasher& ph + , std::ptrdiff_t const len + , piece_index_t const piece + , int const offset, error_code& ec) + { + return do_hashv(ph, len, piece, offset, ec); + } + + int posix_part_file::hashv2(hasher256& ph + , std::ptrdiff_t const len + , piece_index_t const piece + , int const offset, error_code& ec) + { + return do_hashv(ph, len, piece, offset, ec); + } + + template + int posix_part_file::do_hashv(Hasher& ph + , std::ptrdiff_t const len + , piece_index_t const piece + , int const offset, error_code& ec) + { + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(len >= 0); + TORRENT_ASSERT(int(len) + offset <= m_piece_size); + + auto const i = m_piece_map.find(piece); + if (i == m_piece_map.end()) + { + ec = error_code(boost::system::errc::no_such_file_or_directory + , boost::system::generic_category()); + return -1; + } + + slot_index_t const slot = i->second; + auto f = open_file(open_mode::read_only, ec); + if (ec) return -1; + + std::vector buffer(static_cast(len)); + std::int64_t const slot_offset = std::int64_t(m_header_size) + std::int64_t(static_cast(slot)) * m_piece_size; + if (portable_fseeko(f.file(), slot_offset + offset, SEEK_SET) != 0) + { + ec.assign(errno, generic_category()); + return -1; + } + auto const ret = std::fread(buffer.data(), 1, buffer.size(), f.file()); + if (ret != buffer.size()) + { + ec.assign(errno, generic_category()); + return -1; + } + ph.update(buffer); + return numeric_cast(ret); + } + + file_pointer posix_part_file::open_file(open_mode const mode, error_code& ec) + { + std::string const fn = combine_path(m_path, m_name); +#ifdef TORRENT_WINDOWS + wchar_t const* mode_str[] = {L"rb", L"rb+"}; + file_pointer ret(::_wfopen(convert_to_native_path_string(fn).c_str() + , mode_str[static_cast(mode)])); +#else + char const* mode_str[] = {"rb", "rb+"}; + file_pointer ret(::fopen(fn.c_str() + , mode_str[static_cast(mode)])); +#endif + if (ret.file() == nullptr) + ec.assign(errno, generic_category()); + + if (mode == open_mode::read_write + && 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 {}; + +#ifdef TORRENT_WINDOWS + ret = file_pointer(::_wfopen(convert_to_native_path_string(fn).c_str(), L"wb+")); +#else + ret = file_pointer(::fopen(fn.c_str(), "wb+")); +#endif + if (ret.file() == nullptr) + ec.assign(errno, generic_category()); + } + if (ec) return {}; + return ret; + } + + void posix_part_file::free_piece(piece_index_t const piece) + { + 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 posix_part_file::move_partfile(std::string const& path, error_code& ec) + { + 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 posix_part_file::export_file(std::function)> f + , std::int64_t const offset, std::int64_t size, error_code& ec) + { + // there's nothing stored in the posix_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)]); + + if (portable_fseeko(file.file(), slot_offset(slot) + piece_offset, SEEK_SET) != 0) + { + ec.assign(errno, generic_category()); + return; + } + auto bytes_read = std::fread(buf.get(), 1, std::size_t(block_to_copy), file.file()); + if (int(bytes_read) != block_to_copy) + ec.assign(errno, generic_category()); + + TORRENT_ASSERT(!ec); + if (ec) return; + + f(file_offset, {buf.get(), block_to_copy}); + } + file_offset += block_to_copy; + piece_offset = 0; + size -= block_to_copy; + } + } + + void posix_part_file::flush_metadata(error_code& ec) + { + flush_metadata_impl(ec); + } + + // TODO: instead of rebuilding the whole file header + // and flushing it, update the slot entries as we go + void posix_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, ec); + if (ec) return; + + std::vector header(static_cast(m_header_size)); + + using namespace libtorrent::aux; + + 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()))); + + auto const written = std::fwrite(header.data(), 1, header.size(), f.file()); + if (written != header.size()) + { + ec.assign(errno, generic_category()); + return; + } + m_dirty_metadata = false; + } +} +} diff --git a/src/posix_storage.cpp b/src/posix_storage.cpp new file mode 100644 index 0000000..0de2a6e --- /dev/null +++ b/src/posix_storage.cpp @@ -0,0 +1,554 @@ +/* + +Copyright (c) 2016, 2019-2020, Arvid Norberg +Copyright (c) 2020, pavel-pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/settings_pack.hpp" +#include "libtorrent/aux_/posix_storage.hpp" +#include "libtorrent/aux_/path.hpp" // for bufs_size +#include "libtorrent/aux_/open_mode.hpp" +#include "libtorrent/aux_/file_pointer.hpp" +#include "libtorrent/torrent_status.hpp" +#ifdef TORRENT_WINDOWS +#include "libtorrent/utf8.hpp" +#endif + +using namespace libtorrent::flags; // for flag operators + +#ifndef TORRENT_WINDOWS +// make sure the _FILE_OFFSET_BITS define worked +// on this platform. It's supposed to make file +// related functions support 64-bit offsets. +#if TORRENT_HAS_FTELLO +static_assert(sizeof(ftello(nullptr)) >= 8, "64 bit file operations are required"); +#endif +static_assert(sizeof(off_t) >= 8, "64 bit file operations are required"); +#endif + +namespace libtorrent { +namespace aux { + + posix_storage::posix_storage(storage_params const& p) + : m_files(p.files) + , m_save_path(p.path) + , m_part_file_name("." + to_hex(p.info_hash) + ".parts") + { + if (p.mapped_files) m_mapped_files.reset(new file_storage(*p.mapped_files)); + } + + file_storage const& posix_storage::files() const { return m_mapped_files ? *m_mapped_files.get() : m_files; } + + posix_storage::~posix_storage() + { + error_code ec; + if (m_part_file) m_part_file->flush_metadata(ec); + } + + void posix_storage::need_partfile() + { + if (m_part_file) return; + + m_part_file = std::make_unique( + m_save_path, m_part_file_name + , files().num_pieces(), files().piece_length()); + } + + void posix_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) + { + if (m_part_file && use_partfile(i)) + { + m_part_file->export_file([this, i, &ec](std::int64_t file_offset, span buf) + { + // move stuff out of the part file + file_pointer const f = open_file(i, open_mode::write, file_offset, ec); + if (ec) return; + int const r = static_cast(fwrite(buf.data(), 1 + , static_cast(buf.size()), f.file())); + if (r != buf.size()) + { + if (ferror(f.file())) ec.ec.assign(errno, generic_category()); + else ec.ec.assign(errors::file_too_short, libtorrent_category()); + return; + } + }, 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); + bool const file_exists = exists(fp, ec.ec); + if (ec.ec) + { + ec.file(i); + ec.operation = operation_t::file_stat; + prio = m_file_priority; + return; + } + use_partfile(i, !file_exists); + } + 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; + } + } + + int posix_storage::readv(settings_interface const& + , span bufs + , piece_index_t const piece, int const offset + , storage_error& error) + { + return readwritev(files(), bufs, piece, offset, error + , [this](file_index_t const file_index + , std::int64_t const file_offset + , span vec, storage_error& ec) + { + // reading from a pad file yields zeroes + if (files().pad_file_at(file_index)) return aux::read_zeroes(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_pointer const f = open_file(file_index, open_mode::read_only + , file_offset, ec); + if (ec.ec) return -1; + + // set this unconditionally in case the upper layer would like to treat + // short reads as errors + ec.operation = operation_t::file_read; + + int ret = 0; + for (auto buf : vec) + { + int const r = static_cast(fread(buf.data(), 1 + , static_cast(buf.size()), f.file())); + if (r == 0) + { + if (ferror(f.file())) ec.ec.assign(errno, generic_category()); + else ec.ec.assign(errors::file_too_short, libtorrent_category()); + break; + } + ret += r; + + // the file may be shorter than we think + if (r < buf.size()) break; + } + + // we either get an error or 0 or more bytes read + TORRENT_ASSERT(ec.ec || ret > 0); + TORRENT_ASSERT(ret <= bufs_size(vec)); + + if (ec.ec) + { + ec.file(file_index); + return -1; + } + + return ret; + }); + } + + int posix_storage::writev(settings_interface const& + , span bufs + , piece_index_t const piece, int const offset + , storage_error& error) + { + return readwritev(files(), bufs, piece, offset, error + , [this](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; + } + + file_pointer const f = open_file(file_index, open_mode::write + , file_offset, ec); + if (ec.ec) return -1; + + // set this unconditionally in case the upper layer would like to treat + // short reads as errors + ec.operation = operation_t::file_write; + + int ret = 0; + for (auto buf : vec) + { + auto const r = static_cast(fwrite(buf.data(), 1 + , static_cast(buf.size()), f.file())); + if (r != buf.size()) + { + if (ferror(f.file())) ec.ec.assign(errno, generic_category()); + else ec.ec.assign(errors::file_too_short, libtorrent_category()); + break; + } + ret += r; + } + + // invalidate our stat cache for this file, since + // we're writing to it + m_stat_cache.set_dirty(file_index); + + if (ec) + { + ec.file(file_index); + return -1; + } + + return ret; + }); + } + + bool posix_storage::has_any_file(storage_error& error) + { + m_stat_cache.reserve(files().num_files()); + return aux::has_any_file(files(), m_save_path, m_stat_cache, error); + } + + bool posix_storage::verify_resume_data(add_torrent_params const& rd + , vector const& links + , storage_error& ec) + { + return aux::verify_resume_data(rd, links, files() + , m_file_priority, m_stat_cache, m_save_path, ec); + } + + void posix_storage::release_files() + { + m_stat_cache.clear(); + if (m_part_file) + { + error_code ignore; + m_part_file->flush_metadata(ignore); + } + } + + void posix_storage::delete_files(remove_flags_t const options, storage_error& error) + { + // 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, error); + } + + std::pair posix_storage::move_storage(std::string const& sp + , move_flags_t const flags, storage_error& ec) + { + lt::status_t ret; + auto move_partfile = [&](std::string const& new_save_path, error_code& e) + { + if (!m_part_file) return; + m_part_file->move_partfile(new_save_path, e); + }; + std::tie(ret, m_save_path) = aux::move_storage(files(), m_save_path, sp + , std::move(move_partfile), flags, ec); + + // clear the stat cache in case the new location has new files + m_stat_cache.clear(); + + return { ret, m_save_path }; + } + + void posix_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 const old_name = files().file_path(index, m_save_path); + + 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 (ec.ec == boost::system::errc::no_such_file_or_directory) + ec.ec.clear(); + + if (ec) + { + ec.file(index); + ec.operation = operation_t::file_rename; + return; + } + } + else if (ec.ec) + { + // if exists fails, report that error + ec.file(index); + ec.operation = operation_t::file_rename; + return; + } + + if (!m_mapped_files) + { + m_mapped_files.reset(new file_storage(files())); + } + m_mapped_files->rename_file(index, new_filename); + } + + status_t posix_storage::initialize(settings_interface const&, storage_error& ec) + { + m_stat_cache.reserve(files().num_files()); + + 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. + status_t ret{}; + 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 (s.file_size > fs.file_size(i)) + ret = ret | status_t::oversized_file; + + if (!err) + { + use_partfile(i, false); + } + else + { + need_partfile(); + } + } + + aux::initialize_storage(fs, m_save_path, m_stat_cache, m_file_priority + , [this](file_index_t const file_index, storage_error& e) + { open_file(file_index, aux::open_mode::write, 0, e); } + , aux::create_symlink + , [&ret](file_index_t, std::int64_t) { ret = ret | status_t::oversized_file; } + , ec); + return ret; + } + + file_pointer posix_storage::open_file(file_index_t idx, open_mode_t const mode + , std::int64_t const offset, storage_error& ec) + { + std::string const fn = files().file_path(idx, m_save_path); + + auto const* mode_str = (mode & open_mode::write) +#ifdef TORRENT_WINDOWS + ? L"rb+" : L"rb"; +#else + ? "rb+" : "rb"; +#endif + +#ifdef TORRENT_WINDOWS + FILE* f = ::_wfopen(convert_to_native_path_string(fn).c_str(), mode_str); +#else + FILE* f = std::fopen(fn.c_str(), mode_str); +#endif + if (f == nullptr) + { + ec.ec.assign(errno, generic_category()); + + // if we fail to open a file for writing, and the error is ENOENT, + // it is likely because the directory we're creating the file in + // does not exist. Create the directory and try again. + if ((mode & aux::open_mode::write) + && (ec.ec == boost::system::errc::no_such_file_or_directory +#ifdef TORRENT_WINDOWS + // this is a workaround for improper handling of files on windows shared drives. + // if the directory on a shared drive does not exist, + // windows returns ERROR_IO_DEVICE instead of ERROR_FILE_NOT_FOUND + || ec.ec == error_code(ERROR_IO_DEVICE, system_category()) +#endif + )) + { + // this means the directory the file is in doesn't exist. + // so create it + ec.ec.clear(); + create_directories(parent_path(fn), ec.ec); + + if (ec.ec) + { + ec.file(idx); + ec.operation = operation_t::mkdir; + return file_pointer{}; + } + + // now that we've created the directories, try again + // and make sure we create the file this time ("r+") opens for + // reading and writing, but doesn't create the file. "w+" creates + // the file and truncates it +#ifdef TORRENT_WINDOWS + f = ::_wfopen(convert_to_native_path_string(fn).c_str(), L"wb+"); +#else + f = std::fopen(fn.c_str(), "wb+"); +#endif + if (f == nullptr) + { + ec.ec.assign(errno, generic_category()); + ec.file(idx); + ec.operation = operation_t::file_open; + return file_pointer{}; + } + } + else + { + ec.file(idx); + ec.operation = operation_t::file_open; + return file_pointer{}; + } + } + + if (offset != 0) + { + if (portable_fseeko(f, offset, SEEK_SET) != 0) + { + ec.ec.assign(errno, generic_category()); + ec.file(idx); + ec.operation = operation_t::file_seek; + return file_pointer{}; + } + } + + return file_pointer{f}; + } + + bool posix_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 posix_storage::use_partfile(file_index_t const index, bool const b) + { + if (index >= m_use_partfile.end_index()) + { + // no need to extend this array if we're just setting it to "true", + // that's default already + if (b) return; + m_use_partfile.resize(static_cast(index) + 1, true); + } + m_use_partfile[index] = b; + } + +} +} diff --git a/src/proxy_base.cpp b/src/proxy_base.cpp new file mode 100644 index 0000000..10c1cd8 --- /dev/null +++ b/src/proxy_base.cpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2010, 2014, 2016-2017, 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 "libtorrent/proxy_base.hpp" + +namespace libtorrent { + + proxy_base::proxy_base(io_context& io_context) + : m_sock(io_context) + , m_port(0) + , m_resolver(io_context) + {} + + proxy_base::~proxy_base() = default; + + static_assert(std::is_nothrow_move_constructible::value + , "should be nothrow move constructible"); +} diff --git a/src/proxy_settings.cpp b/src/proxy_settings.cpp new file mode 100644 index 0000000..01fa002 --- /dev/null +++ b/src/proxy_settings.cpp @@ -0,0 +1,67 @@ +/* + +Copyright (c) 2015, 2017-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 "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..c70e67e --- /dev/null +++ b/src/random.cpp @@ -0,0 +1,198 @@ +/* + +Copyright (c) 2011-2012, 2014-2015, 2017-2020, Arvid Norberg +Copyright (c) 2016, 2019, Alden Torres +Copyright (c) 2017, 2019, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_/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_CNG +#include "libtorrent/aux_/win_cng.hpp" + +#elif TORRENT_USE_CRYPTOAPI +#include "libtorrent/aux_/win_crypto_provider.hpp" + +#elif defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_WOLFSSL + +#include "libtorrent/aux_/disable_warnings_push.hpp" +extern "C" { +#include +#include +} +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#elif TORRENT_USE_GETRANDOM + +#include +// this is the fall-back in case getrandom() fails +#include "libtorrent/aux_/dev_random.hpp" + +#elif 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 + { + std::uint32_t ret; + crypto_random_bytes({reinterpret_cast(&ret), sizeof(ret)}); + return ret; + } + } dev; +#else + static std::random_device dev; +#endif +#ifdef BOOST_NO_CXX11_THREAD_LOCAL + static std::seed_seq seed({dev(), dev(), dev(), dev()}); + static std::mt19937 rng(seed); +#else + thread_local static std::seed_seq seed({dev(), dev(), dev(), dev()}); + thread_local static std::mt19937 rng(seed); +#endif +#endif + return rng; + } + + void random_bytes(span buffer) + { +#ifdef TORRENT_BUILD_SIMULATOR + // simulator + for (auto& b : buffer) b = char(random(0xff)); +#else + std::generate(buffer.begin(), buffer.end(), [] { return char(random(0xff)); }); +#endif + } + + void crypto_random_bytes(span buffer) + { +#ifdef TORRENT_BUILD_SIMULATOR + // In the simulator we want deterministic random numbers + std::generate(buffer.begin(), buffer.end(), [] { return char(random(0xff)); }); +#elif TORRENT_USE_CNG + aux::cng_gen_random(buffer); +#elif TORRENT_USE_CRYPTOAPI + // windows + aux::crypt_gen_random(buffer); +#elif defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_WOLFSSL +// wolfSSL uses wc_RNG_GenerateBlock as the internal function for the +// openssl compatibility layer. This function API does not support +// an arbitrary buffer size (openssl does), it is limited by the +// constant RNG_MAX_BLOCK_LEN. +// TODO: improve calling RAND_bytes multiple times, using fallback for now + + // openssl + int r = RAND_bytes(reinterpret_cast(buffer.data()) + , int(buffer.size())); + if (r != 1) aux::throw_ex(errors::no_entropy); +#elif TORRENT_USE_GETRANDOM + ssize_t const r = ::getrandom(buffer.data(), static_cast(buffer.size()), 0); + if (r == ssize_t(buffer.size())) return; + if (r == -1 && errno != ENOSYS) aux::throw_ex(error_code(errno, generic_category())); + static dev_random dev; + dev.read(buffer); +#elif TORRENT_USE_DEV_RANDOM + static dev_random dev; + dev.read(buffer); +#else + +#if TORRENT_BROKEN_RANDOM_DEVICE + // even pseudo random numbers rely on being able to seed the random + // generator +#error "no entropy source available" +#else +#ifdef TORRENT_I_WANT_INSECURE_RANDOM_NUMBERS + std::generate(buffer.begin(), buffer.end(), [] { return char(random(0xff)); }); +#else +#error "no secure entropy source available. If you really want insecure random numbers, define TORRENT_I_WANT_INSECURE_RANDOM_NUMBERS" +#endif +#endif + +#endif + } +} + + std::uint32_t random(std::uint32_t const max) + { + if (max == 0) return 0; +#ifdef BOOST_NO_CXX11_THREAD_LOCAL + std::lock_guard l(rng_mutex); +#endif +#ifdef TORRENT_BUILD_SIMULATOR + std::uint32_t mask = max | (max >> 16); + mask |= mask >> 8; + mask |= mask >> 4; + mask |= mask >> 2; + mask |= mask >> 1; + auto& rng = aux::random_engine(); + std::uint32_t ret; + do + { + ret = rng() & mask; + } while (ret > max); +#else + auto const ret = std::uniform_int_distribution(0, max)(aux::random_engine()); +#endif + return ret; + } + +} diff --git a/src/read_resume_data.cpp b/src/read_resume_data.cpp new file mode 100644 index 0000000..0de296f --- /dev/null +++ b/src/read_resume_data.cpp @@ -0,0 +1,444 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2017, Steven Siloti +Copyright (c) 2020, Vladimir Golovnev (glassez) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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, (flag & torrent_flags::default_flags) ? 1 : 0) == 0) + { + current_flags &= ~flag; + } + else + { + current_flags |= flag; + } + } + +} // anonyous namespace + + add_torrent_params read_resume_data(bdecode_node const& rd, error_code& ec + , int const piece_limit) + { + 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"); + auto info_hash2 = rd.dict_find_string_value("info-hash2"); + if (info_hash.size() != std::size_t(sha1_hash::size()) + && info_hash2.size() != std::size_t(sha256_hash::size())) + { + ec = errors::missing_info_hash; + return ret; + } + + ret.name = rd.dict_find_string_value("name").to_string(); + + if (info_hash.size() == 20) + ret.info_hashes.v1.assign(info_hash.data()); + if (info_hash2.size() == 32) + ret.info_hashes.v2.assign(info_hash2.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 + info_hash_t const resume_ih(hasher(info.data_section()).final() + , hasher256(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 ((!ret.info_hashes.has_v1() || resume_ih.v1 == ret.info_hashes.v1) + && (!ret.info_hashes.has_v2() || resume_ih.v2 == ret.info_hashes.v2)) + { + ret.ti = std::make_shared(resume_ih); + + error_code err; + if (!ret.ti->parse_info_section(info, err, piece_limit)) + { + 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", "")); + } + } + } + +#if TORRENT_ABI_VERSION < 3 + ret.info_hash = ret.info_hashes.get_best(); +#endif + + bdecode_node const trees = rd.dict_find_list("trees"); + if (trees) + { + ret.merkle_trees.reserve(trees.list_size()); + ret.verified_leaf_hashes.reserve(trees.list_size()); + for (int i = 0; i < trees.list_size(); ++i) + { + auto de = trees.list_at(i); + if (de.type() != bdecode_node::dict_t) + break; + auto dh = de.dict_find_string("hashes"); + if (!dh || dh.string_length() % 32 != 0) break; + + ret.merkle_trees.emplace_back(); + ret.merkle_trees.back().reserve(dh.string_value().size() / 32); + for (auto hashes = dh.string_value(); + !hashes.empty(); hashes = hashes.substr(32)) + { + ret.merkle_trees.back().emplace_back(hashes); + } + + auto const verified = de.dict_find_string_value("verified"); + ret.verified_leaf_hashes.emplace_back(); + ret.verified_leaf_hashes.back().reserve(verified.size()); + for (auto const bit : verified) + ret.verified_leaf_hashes.back().emplace_back(bit == '1'); + + auto const mask = de.dict_find_string_value("mask"); + ret.merkle_tree_mask.emplace_back(); + ret.merkle_tree_mask.back().reserve(mask.size()); + for (auto const bit : mask) + ret.merkle_tree_mask.back().emplace_back(bit == '1'); + } + } + + 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(); +#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) + { + auto const idx = static_cast(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()); + } + } + + // 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::aux; // 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 + , load_torrent_limits const& cfg) + { + int pos; + bdecode_node rd = bdecode(buffer, ec, &pos, cfg.max_decode_depth + , cfg.max_decode_tokens); + if (ec) return add_torrent_params(); + + return read_resume_data(rd, ec, cfg.max_pieces); + } + + add_torrent_params read_resume_data(bdecode_node const& rd, int const piece_limit) + { + error_code ec; + auto ret = read_resume_data(rd, ec, piece_limit); + if (ec) throw system_error(ec); + return ret; + } + + add_torrent_params read_resume_data(span buffer + , load_torrent_limits const& cfg) + { + int pos; + error_code ec; + bdecode_node rd = bdecode(buffer, ec, &pos, cfg.max_decode_depth + , cfg.max_decode_tokens); + if (ec) throw system_error(ec); + + auto ret = read_resume_data(rd, ec, cfg.max_pieces); + 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..691b3df --- /dev/null +++ b/src/receive_buffer.cpp @@ -0,0 +1,341 @@ +/* + +Copyright (c) 2014-2016, 2018-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2018, 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_/receive_buffer.hpp" +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/span.hpp" + +namespace libtorrent { +namespace aux { + +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 aux +} // namespace libtorrent diff --git a/src/request_blocks.cpp b/src/request_blocks.cpp new file mode 100644 index 0000000..74a3647 --- /dev/null +++ b/src/request_blocks.cpp @@ -0,0 +1,310 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016, 2019-2020, 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/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/aux_/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 = 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 endgame: %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); + + // 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; + + 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 + auto const 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..b392387 --- /dev/null +++ b/src/resolve_links.cpp @@ -0,0 +1,188 @@ +/* + +Copyright (c) 2015-2019, Arvid Norberg +Copyright (c) 2016-2017, 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/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(std::move(ti)) +{ + TORRENT_ASSERT(m_torrent_file); + + int const piece_size = m_torrent_file->piece_length(); + + bool const v1 = m_torrent_file->v1(); + bool const v2 = m_torrent_file->v2(); + + file_storage const& fs = m_torrent_file->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; + + if (v1) + m_file_sizes.insert({fs.file_size(i), i}); + + if (v2) + m_file_roots.insert({fs.root(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; + + if (m_torrent_file->v2() && ti->v2()) + { + match_v2(ti, save_path); + } + + if (m_torrent_file->v1() && ti->v1()) + { + match_v1(ti, save_path); + } +} + +void resolve_links::match_v1(std::shared_ptr const& ti + , std::string const& save_path) +{ + // only torrents with the same piece size + if (ti->piece_length() != m_torrent_file->piece_length()) return; + + int const piece_size = ti->piece_length(); + + file_storage const& fs = ti->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) + { + auto const idx = iter->second; + TORRENT_ASSERT(idx >= file_index_t(0)); + TORRENT_ASSERT(idx < m_torrent_file->files().end_file()); + + // if we already have found a duplicate for this file, no need + // to keep looking + if (m_links[idx].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( + idx, 0, 0).piece; + + int const 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[idx].ti = ti; + m_links[idx].save_path = save_path; + m_links[idx].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; + } + } +} + +void resolve_links::match_v2(std::shared_ptr const& ti + , std::string const& save_path) +{ + file_storage const& fs = ti->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 (fs.pad_file_at(i)) continue; + + auto const iter = m_file_roots.find(fs.root(i)); + if (iter == m_file_roots.end()) continue; + + auto const idx = iter->second; + + TORRENT_ASSERT(idx >= file_index_t(0)); + TORRENT_ASSERT(idx < m_torrent_file->files().end_file()); + + // if we already have found a duplicate for this file, no need + // to keep looking + if (m_links[idx].ti) continue; + + m_links[idx].ti = ti; + m_links[idx].save_path = save_path; + m_links[idx].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_roots.erase(iter); + } +} + +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + +} // namespace libtorrent diff --git a/src/resolver.cpp b/src/resolver.cpp new file mode 100644 index 0000000..796ffb5 --- /dev/null +++ b/src/resolver.cpp @@ -0,0 +1,176 @@ +/* + +Copyright (c) 2014-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016-2018, 2020, 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/aux_/resolver.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/aux_/time.hpp" + +namespace libtorrent { +namespace aux { + + + constexpr resolver_flags resolver_interface::cache_only; + constexpr resolver_flags resolver_interface::abort_on_shutdown; + + resolver::resolver(io_context& 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::results_type ips + , 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 = time_now(); + ce.addresses.clear(); + for (auto i : ips) + ce.addresses.push_back(i.endpoint().address()); + + 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) + { + post(m_ios, [=]{ 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 >= time_now()) + { + std::vector
    ips = i->second.addresses; + post(m_ios, [=] { callback(h, ec, ips); }); + return; + } + } + + if (flags & resolver_interface::cache_only) + { + // we did not find a cache entry, fail the lookup + post(m_ios, [=] { + 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 + using namespace std::placeholders; + ADD_OUTSTANDING_ASYNC("resolver::on_lookup"); + if (flags & resolver_interface::abort_on_shutdown) + { + m_resolver.async_resolve(host, "80", std::bind(&resolver::on_lookup, this, _1, _2 + , host)); + } + else + { + m_critical_resolver.async_resolve(host, "80", 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..380c860 --- /dev/null +++ b/src/session.cpp @@ -0,0 +1,551 @@ +/* + +Copyright (c) 2003, Magnus Jonsson +Copyright (c) 2003, 2006, 2008-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, 2020, 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/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 +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/mmap_disk_io.hpp" +#include "libtorrent/posix_disk_io.hpp" +#include "libtorrent/platform_util.hpp" + +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(milliseconds(300)); + ++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); + + 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); + 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); + + // 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; + } + + void session::start(session_flags_t const flags, session_params&& params, io_context* 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(); + } + +#if TORRENT_ABI_VERSION <= 2 +#ifndef TORRENT_DISABLE_DHT + // in case the session_params has its dht_settings in use, pick out the + // non-default settings from there and move them into the main settings. + // any conflicting options set in main settings take precedence + { + dht::dht_settings const def_sett{}; +#define SET_BOOL(name) if (!params.settings.has_val(settings_pack::dht_ ## name) && \ + def_sett.name != params.dht_settings.name) \ + params.settings.set_bool(settings_pack::dht_ ## name, params.dht_settings.name) +#define SET_INT(name) if (!params.settings.has_val(settings_pack::dht_ ## name) && \ + def_sett.name != params.dht_settings.name) \ + params.settings.set_int(settings_pack::dht_ ## name, params.dht_settings.name) + + SET_INT(max_peers_reply); + SET_INT(search_branching); + SET_INT(max_fail_count); + SET_INT(max_torrents); + SET_INT(max_dht_items); + SET_INT(max_peers); + SET_INT(max_torrent_search_reply); + SET_BOOL(restrict_routing_ips); + SET_BOOL(restrict_search_ips); + SET_BOOL(extended_routing_table); + SET_BOOL(aggressive_lookups); + SET_BOOL(privacy_lookups); + SET_BOOL(enforce_node_id); + SET_BOOL(ignore_dark_internet); + SET_INT(block_timeout); + SET_INT(block_ratelimit); + SET_BOOL(read_only); + SET_INT(item_lifetime); + SET_INT(upload_rate_limit); + SET_INT(sample_infohashes_interval); + SET_INT(max_infohashes_sample_count); +#undef SET_BOOL +#undef SET_INT + } +#endif +#endif + + m_impl = std::make_shared(std::ref(*ios) + , std::move(params.settings) + , std::move(params.disk_io_constructor) + , flags); + *static_cast(this) = session_handle(m_impl); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& ext : params.extensions) + { + ext->load_state(params.ext_state); + m_impl->add_ses_extension(std::move(ext)); + } +#endif + +#ifndef TORRENT_DISABLE_DHT + 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 + + if (!params.ip_filter.empty()) + { + std::shared_ptr copy = std::make_shared(std::move(params.ip_filter)); + m_impl->set_ip_filter(std::move(copy)); + } + + m_impl->start_session(); + + if (internal_executor) + { + // start a thread for the message pump + auto s = m_io_service; + m_thread = std::make_shared([=] + { + set_thread_name("Network"); + s->run(); + }); + } + } + +#if TORRENT_ABI_VERSION <= 2 + void session::start(session_flags_t const flags, settings_pack&& sp, io_context* ios) + { + if (flags & add_default_plugins) + { + session_params sp_(std::move(sp)); + start(flags, std::move(sp_), ios); + } + else + { + session_params sp_(std::move(sp), {}); + start(flags, std::move(sp_), ios); + } + } +#endif + + session::session(session&&) = default; + + session::session(session_params const& params) + { + start({}, session_params(params), nullptr); + } + + session::session(session_params&& params) + { + start({}, std::move(params), nullptr); + } + + session::session(session_params const& params, session_flags_t const flags) + { + start(flags, session_params(params), nullptr); + } + + session::session(session_params&& params, session_flags_t const flags) + { + start(flags, std::move(params), nullptr); + } + + session::session() + { + session_params params; + start({}, std::move(params), nullptr); + } + + session::session(session_params&& params, io_context& ios) + { + start({}, std::move(params), &ios); + } + + session::session(session_params const& params, io_context& ios) + { + start({}, session_params(params), &ios); + } + + session::session(session_params&& params, io_context& ios, session_flags_t const flags) + { + start(flags, std::move(params), &ios); + } + + session::session(session_params const& params, io_context& ios, session_flags_t const flags) + { + start(flags, session_params(params), &ios); + } + + +#if TORRENT_ABI_VERSION <= 2 + session::session(settings_pack&& pack, session_flags_t const flags) + { + start(flags, std::move(pack), nullptr); + } + + session::session(settings_pack const& pack, session_flags_t const flags) + { + start(flags, settings_pack(pack), nullptr); + } + + session::session(settings_pack&& pack, io_context& ios, session_flags_t const flags) + { + start(flags, std::move(pack), &ios); + } + + session::session(settings_pack const& pack, io_context& ios, session_flags_t const flags) + { + start(flags, settings_pack(pack), &ios); + } +#endif + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + session::session(fingerprint const& print, session_flags_t const flags + , alert_category_t const alert_mask) + { + 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); + } + + session::session(fingerprint const& print, std::pair listen_port_range + , char const* listen_interface, session_flags_t const flags + , alert_category_t const alert_mask) + { + 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); + } +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // TORRENT_ABI_VERSION + session& session::operator=(session&&) & = default; + + 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(); + } + } + + TORRENT_EXPORT std::unique_ptr default_disk_io_constructor( + io_context& ios, settings_interface const& sett, counters& cnt) + { +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + // TODO: In C++17. use if constexpr instead +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + if (sizeof(void*) == 8) + return mmap_disk_io_constructor(ios, sett, cnt); + else + return posix_disk_io_constructor(ios, sett, cnt); +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#else + return posix_disk_io_constructor(ios, sett, cnt); +#endif + } + +} diff --git a/src/session_call.cpp b/src/session_call.cpp new file mode 100644 index 0000000..760e2af --- /dev/null +++ b/src/session_call.cpp @@ -0,0 +1,82 @@ +/* + +Copyright (c) 2014-2019, Steven Siloti +Copyright (c) 2014, 2016, 2019, Arvid Norberg +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/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..96828a4 --- /dev/null +++ b/src/session_handle.cpp @@ -0,0 +1,1267 @@ +/* + +Copyright (c) 2014-2018, Steven Siloti +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2015-2018, 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/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/hasher.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" +#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; +#if TORRENT_ABI_VERSION <= 2 + constexpr save_state_flags_t session_handle::save_dht_settings TORRENT_DEPRECATED; +#endif + 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 save_state_flags_t session_handle::save_extension_state; + constexpr save_state_flags_t session_handle::save_ip_filter; + +#if TORRENT_ABI_VERSION <= 2 + constexpr session_flags_t session_handle::add_default_plugins; +#endif +#if TORRENT_ABI_VERSION == 1 + constexpr session_flags_t session_handle::start_default_features; +#endif + constexpr session_flags_t session_handle::paused; + + 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); + dispatch(s->get_context(), [=]() 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; + dispatch(s->get_context(), [=, &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; + dispatch(s->get_context(), [=, &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; + } + +#if TORRENT_ABI_VERSION <= 2 + 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); + } +#endif + + session_params session_handle::session_state(save_state_flags_t const flags) const + { + return sync_call_ret(&session_impl::session_state, 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_context& session_handle::get_context() + { + std::shared_ptr s = m_impl.lock(); + if (!s) aux::throw_ex(errors::invalid_session_handle); + return s->get_context(); + } + + void session_handle::set_dht_state(dht::dht_state const& st) + { +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::set_dht_state, dht::dht_state(st)); +#else + TORRENT_UNUSED(st); +#endif + } + + void session_handle::set_dht_state(dht::dht_state&& st) + { +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::set_dht_state, std::move(st)); +#else + TORRENT_UNUSED(st); +#endif + } + + 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.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.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 < 3 + if (!params.info_hashes.has_v1() && !params.info_hashes.has_v2() && !params.ti) + params.info_hashes.v1 = params.info_hash; +#endif + + // the internal torrent object keeps and mutates state in the + // torrent_info object. We can't let that leak back to the client + if (params.ti) + params.ti = std::make_shared(*params.ti); + +#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()); + +#if TORRENT_ABI_VERSION < 3 + if (!params.info_hashes.has_v1() && !params.info_hashes.has_v2() && !params.ti) + params.info_hashes.v1 = params.info_hash; +#endif + + // the internal torrent object keeps and mutates state in the + // torrent_info object. We can't let that leak back to the client + if (params.ti) + params.ti = std::make_shared(*params.ti); + + 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()); + +#if TORRENT_ABI_VERSION < 3 + if (!params.info_hashes.has_v1() && !params.info_hashes.has_v2() && !params.ti) + params.info_hashes.v1 = params.info_hash; +#endif + + // the internal torrent object keeps and mutates state in the + // torrent_info object. We can't let that leak back to the client + if (params.ti) + params.ti = std::make_shared(*params.ti); + + // 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 const add_paused) + { + add_torrent_params p; + 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 (add_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 const add_paused + , client_data_t userdata) + { + TORRENT_ASSERT_PRECOND(!save_path.empty()); + + add_torrent_params p; + p.trackers.push_back(tracker_url); + p.info_hashes.v1 = info_hash; + p.save_path = save_path; + p.storage_mode = storage_mode; + + if (add_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::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 + +#if TORRENT_ABI_VERSION <= 2 + 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 + } +#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 + , client_data_t 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&, client_data_t)> 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_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; + } +#endif // TORRENT_ABI_VERSION + + void session_handle::set_ip_filter(ip_filter f) + { + std::shared_ptr copy = std::make_shared(std::move(f)); + async_call(&session_impl::set_ip_filter, std::move(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 + 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..1638942 --- /dev/null +++ b/src/session_impl.cpp @@ -0,0 +1,7295 @@ +/* + +Copyright (c) 2003, Magnus Jonsson +Copyright (c) 2006-2020, Arvid Norberg +Copyright (c) 2009, Andrew Resch +Copyright (c) 2014-2020, Steven Siloti +Copyright (c) 2015-2020, Alden Torres +Copyright (c) 2015, Thomas +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016, Falcosc +Copyright (c) 2016-2017, Pavel Pimenov +Copyright (c) 2016-2017, Andrei Kurushin +Copyright (c) 2017, sledgehammer_999 +Copyright (c) 2018, Xiyue Deng +Copyright (c) 2020, Fonic +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/ssl.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/aux_/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" +#include "libtorrent/aux_/array.hpp" +#include "libtorrent/aux_/set_traffic_class.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 +#ifdef TORRENT_WINDOWS +#include +#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; + + std::map _handler_storage; + std::mutex _handler_storage_mutex; + bool _handler_logger_registered = false; +#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; + +void apply_deprecated_dht_settings(settings_pack& sett, bdecode_node const& s) +{ + bdecode_node val; + val = s.dict_find_int("max_peers_reply"); + if (val) sett.set_int(settings_pack::dht_max_peers_reply, int(val.int_value())); + val = s.dict_find_int("search_branching"); + if (val) sett.set_int(settings_pack::dht_search_branching, int(val.int_value())); + val = s.dict_find_int("max_fail_count"); + if (val) sett.set_int(settings_pack::dht_max_fail_count, int(val.int_value())); + val = s.dict_find_int("max_torrents"); + if (val) sett.set_int(settings_pack::dht_max_torrents, int(val.int_value())); + val = s.dict_find_int("max_dht_items"); + if (val) sett.set_int(settings_pack::dht_max_dht_items, int(val.int_value())); + val = s.dict_find_int("max_peers"); + if (val) sett.set_int(settings_pack::dht_max_peers, int(val.int_value())); + val = s.dict_find_int("max_torrent_search_reply"); + if (val) sett.set_int(settings_pack::dht_max_torrent_search_reply, int(val.int_value())); + val = s.dict_find_int("restrict_routing_ips"); + if (val) sett.set_bool(settings_pack::dht_restrict_routing_ips, (val.int_value() != 0)); + val = s.dict_find_int("restrict_search_ips"); + if (val) sett.set_bool(settings_pack::dht_restrict_search_ips, (val.int_value() != 0)); + val = s.dict_find_int("extended_routing_table"); + if (val) sett.set_bool(settings_pack::dht_extended_routing_table, (val.int_value() != 0)); + val = s.dict_find_int("aggressive_lookups"); + if (val) sett.set_bool(settings_pack::dht_aggressive_lookups, (val.int_value() != 0)); + val = s.dict_find_int("privacy_lookups"); + if (val) sett.set_bool(settings_pack::dht_privacy_lookups, (val.int_value() != 0)); + val = s.dict_find_int("enforce_node_id"); + if (val) sett.set_bool(settings_pack::dht_enforce_node_id, (val.int_value() != 0)); + val = s.dict_find_int("ignore_dark_internet"); + if (val) sett.set_bool(settings_pack::dht_ignore_dark_internet, (val.int_value() != 0)); + val = s.dict_find_int("block_timeout"); + if (val) sett.set_int(settings_pack::dht_block_timeout, int(val.int_value())); + val = s.dict_find_int("block_ratelimit"); + if (val) sett.set_int(settings_pack::dht_block_ratelimit, int(val.int_value())); + val = s.dict_find_int("read_only"); + if (val) sett.set_bool(settings_pack::dht_read_only, (val.int_value() != 0)); + val = s.dict_find_int("item_lifetime"); + if (val) sett.set_int(settings_pack::dht_item_lifetime, int(val.int_value())); +} + + 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 + // if "routes" does not contain a single route to the internet, + // we don't use the last case. On MacOS, we can be notified of + // network changes *before* the routing table is updated + 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_any_internet_route(routes) + && !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); + } + } + +#ifdef TORRENT_SSL_PEERS + 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 + bool ssl_server_name_callback_impl(ssl::stream_handle_type stream_handle, std::string const& name, session_impl* si) + { + if (name.size() < 40) + return false; + + info_hash_t info_hash; + bool valid = aux::from_hex({name.c_str(), 40}, info_hash.v1.data()); + + // the server name is not a valid hex-encoded info-hash + if (!valid) + return false; + + // see if there is a torrent with this info-hash + std::shared_ptr t = si ? si->find_torrent(info_hash).lock() : nullptr; + + // if there isn't, fail + if (!t) return false; + + // if the torrent we found isn't an SSL torrent, also fail. + if (!t->is_ssl_torrent()) return false; + + // if the torrent doesn't have an SSL context and should not allow + // incoming SSL connections + auto* torrent_ctx = t->ssl_ctx(); + if (!torrent_ctx) return false; + + // use this torrent's certificate + ssl::set_context(stream_handle, ssl::get_handle(*torrent_ctx)); + return true; + } + +#if defined TORRENT_USE_OPENSSL +int ssl_server_name_callback(SSL* s, int*, void* arg) +{ + char const* name = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); + auto* si = reinterpret_cast(arg); + return ssl_server_name_callback_impl(s, name ? std::string(name) : "", si) + ? SSL_TLSEXT_ERR_OK + : SSL_TLSEXT_ERR_ALERT_FATAL; +} +#elif defined TORRENT_USE_GNUTLS +bool ssl_server_name_callback(ssl::stream_handle_type stream_handle, std::string const& name, void* arg) +{ + session_impl* si = reinterpret_cast(arg); + return ssl_server_name_callback_impl(stream_handle, name, si); +} +#endif + } // anonymous namespace +#endif // TORRENT_SSL_PEERS + + session_impl::session_impl(io_context& ioc, settings_pack const& pack + , disk_io_constructor_type disk_io_constructor + , session_flags_t const flags) + : m_settings(pack) + , m_io_context(ioc) +#if TORRENT_USE_SSL + , m_ssl_ctx(ssl::context::tls_client) +#ifdef TORRENT_SSL_PEERS + , m_peer_ssl_ctx(ssl::context::tls) +#endif +#endif // TORRENT_USE_SSL + , 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((disk_io_constructor ? disk_io_constructor : default_disk_io_constructor) + (m_io_context, m_settings, m_stats_counters)) + , m_download_rate(peer_connection::download_channel) + , m_upload_rate(peer_connection::upload_channel) + , m_host_resolver(m_io_context) + , 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(make_work_guard(m_io_context)) +#if TORRENT_USE_I2P + , m_i2p_conn(m_io_context) +#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_context) +#endif + , m_utp_socket_manager( + std::bind(&session_impl::send_udp_packet, this, _1, _2, _3, _4, _5) + , [this](socket_type s) { this->incoming_connection(std::move(s)); } + , m_io_context + , m_settings, m_stats_counters, nullptr) +#ifdef TORRENT_SSL_PEERS + , 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_context + , m_settings, m_stats_counters + , &m_peer_ssl_ctx) +#endif + , m_timer(m_io_context) + , m_lsd_announce_timer(m_io_context) + , m_close_file_timer(m_io_context) + , m_paused(flags & session::paused) + { + } + + 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_context thread. + // TODO: 2 is there a reason not to move all of this into init()? and just + // post it to the io_context? + void session_impl::start_session() + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("start session"); +#endif + +#if TORRENT_USE_SSL + error_code ec; + m_ssl_ctx.set_default_verify_paths(ec); +#ifndef TORRENT_DISABLE_LOGGING + if (ec) session_log("SSL set_default verify_paths failed: %s", ec.message().c_str()); + ec.clear(); +#endif +#if defined TORRENT_WINDOWS && defined TORRENT_USE_OPENSSL + // TODO: come up with some abstraction to do this for gnutls as well + // load certificates from the windows system certificate store + X509_STORE* store = X509_STORE_new(); + if (store) + { + HCERTSTORE system_store = CertOpenSystemStoreA(0, "ROOT"); + // this is best effort + if (system_store) + { + CERT_CONTEXT const* ctx = nullptr; + while ((ctx = CertEnumCertificatesInStore(system_store, ctx)) != nullptr) + { + unsigned char const* cert_ptr = reinterpret_cast(ctx->pbCertEncoded); + X509* x509 = d2i_X509(nullptr, &cert_ptr, ctx->cbCertEncoded); + // this is best effort + if (!x509) continue; + X509_STORE_add_cert(store, x509); + X509_free(x509); + } + CertFreeCertificateContext(ctx); + CertCloseStore(system_store, 0); + } + } + + SSL_CTX* ssl_ctx = m_ssl_ctx.native_handle(); + SSL_CTX_set_cert_store(ssl_ctx, store); +#endif +#ifdef __APPLE__ + m_ssl_ctx.load_verify_file("/etc/ssl/cert.pem", ec); +#ifndef TORRENT_DISABLE_LOGGING + if (ec) session_log("SSL load_verify_file failed: %s", ec.message().c_str()); + ec.clear(); +#endif + m_ssl_ctx.add_verify_path("/etc/ssl/certs", ec); +#ifndef TORRENT_DISABLE_LOGGING + if (ec) session_log("SSL add_verify_path failed: %s", ec.message().c_str()); + ec.clear(); +#endif +#endif // __APPLE__ +#endif // TORRENT_USE_SSL +#ifdef TORRENT_SSL_PEERS + m_peer_ssl_ctx.set_verify_mode(ssl::context::verify_none, ec); + ssl::set_server_name_callback(ssl::get_handle(m_peer_ssl_ctx), ssl_server_name_callback, this, ec); +#endif // TORRENT_SSL_PEERS + +#ifndef TORRENT_DISABLE_DHT + m_next_dht_torrent = 0; +#endif + m_next_lsd_torrent = 0; + + 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: %" PRIx64 + , lt::version_str, lt::version_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 + + post(m_io_context, [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 + post(m_io_context, [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); + m_lsd_announce_timer.expires_after(seconds(delay)); + ADD_OUTSTANDING_ASYNC("session_impl::on_lsd_announce"); + m_lsd_announce_timer.async_wait([this](error_code const& e) { + wrap(&session_impl::on_lsd_announce, e); } ); + +#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 + } + +#if TORRENT_ABI_VERSION <= 2 + // 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(non_default_settings(m_settings), sett); + } + +#ifndef TORRENT_DISABLE_DHT + if (flags & session::save_dht_settings) + { + e["dht"] = dht::save_dht_settings(get_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 + } + + 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_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) +#if TORRENT_ABI_VERSION <= 2 + || (flags & session_handle::save_dht_settings) +#endif + ) + { + 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 + } + } + +#if TORRENT_ABI_VERSION <= 2 + if (flags & session_handle::save_dht_settings) +#endif + { + // This is here for backwards compatibility, to support loading state + // files in the previous file format, where the DHT settings were in + // its own dictionary + settings = e->dict_find_dict("dht"); + if (settings) + { + settings_pack sett; + aux::apply_deprecated_dht_settings(sett, settings); + apply_settings_pack_impl(sett); + } + } + +#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 +#if TORRENT_ABI_VERSION <= 2 + for (auto& ext : m_ses_extensions[plugins_all_idx]) + { + ext->load_state(*e); + } +#endif +#endif + } +#endif + + session_params session_impl::session_state(save_state_flags_t const flags) const + { + TORRENT_ASSERT(is_single_thread()); + + session_params ret; + if (flags & session::save_settings) + ret.settings = non_default_settings(m_settings); + +#ifndef TORRENT_DISABLE_DHT +#if TORRENT_ABI_VERSION <= 2 + if (flags & session_handle::save_dht_settings) + { + ret.dht_settings = get_dht_settings(); + } +#endif + + if (m_dht && (flags & session::save_dht_state)) + ret.dht_state = m_dht->state(); +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + if (flags & session::save_extension_state) + { + for (auto const& ext : m_ses_extensions[plugins_all_idx]) + { + auto state = ext->save_state(); + for (auto& v : state) + ret.ext_state[std::move(v.first)] = std::move(v.second); + } + } +#endif + + if ((flags & session::save_ip_filter) && m_ip_filter) + { + ret.ip_filter = *m_ip_filter; + } + return ret; + } + + proxy_settings session_impl::proxy() const + { + return proxy_settings(m_settings); + } + +#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->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->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({}); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& ext : m_ses_extensions[plugins_all_idx]) + { + ext->abort(); + } +#endif + + // 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; + + // we rely on on_tick() during shutdown, but we don't need to wait a + // whole second for it to fire + m_timer.cancel(); + +#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(); +#endif + m_lsd_announce_timer.cancel(); + +#ifdef TORRENT_SSL_PEERS + { + auto const sockets = std::move(m_incoming_sockets); + for (auto const& s : sockets) + { + s->close(ec); + TORRENT_ASSERT(!ec); + } + } +#endif + +#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->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.stop(); + +#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()) + { + post(m_io_context, 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->port_filter_updated(); + } + + void session_impl::set_ip_filter(std::shared_ptr f) + { + INVARIANT_CHECK; + + m_ip_filter = std::move(f); + + // Close connections whose endpoint is filtered + // by the new ip-filter + for (auto& i : m_torrents) + i->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->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 + +#if TORRENT_USE_SSL +#ifdef TORRENT_SSL_PEERS + bool const use_ssl = req.ssl_ctx != nullptr && req.ssl_ctx != &m_ssl_ctx; + if (!use_ssl) +#endif + req.ssl_ctx = &m_ssl_ctx; +#endif + + TORRENT_ASSERT(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_SSL_PEERS + // 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_context(), std::move(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, socket_type_t 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 aux::array const mapping{{{ + 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; + post(m_io_context, make_handler( + [this] { wrap(&session_impl::submit_disk_jobs); } + , m_submit_jobs_handler_storage, *this)); + } + + 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; + } + +namespace { + template + int get_setting_impl(Pack const& p, int name, int*) + { return p.get_int(name); } + + template + bool get_setting_impl(Pack const& p, int name, bool*) + { return p.get_bool(name); } + + template + std::string get_setting_impl(Pack const& p, int name, std::string*) + { return p.get_str(name); } + + template + Type get_setting(Pack const& p, int name) + { + return get_setting_impl(p, name, static_cast(nullptr)); + } + + template + bool setting_changed(settings_pack const& pack, aux::session_settings const& sett, int name) + { + return pack.has_val(name) + && get_setting(pack, name) != get_setting(sett, name); + } +} + + void session_impl::apply_settings_pack_impl(settings_pack const& pack) + { + bool const reopen_listen_port + = setting_changed(pack, m_settings, settings_pack::listen_interfaces) + || setting_changed(pack, m_settings, settings_pack::proxy_type) + || setting_changed(pack, m_settings, settings_pack::proxy_peer_connections) +#if TORRENT_ABI_VERSION == 1 + || setting_changed(pack, m_settings, settings_pack::ssl_listen) +#endif + ; + + bool const update_want_peers + = setting_changed(pack, m_settings, settings_pack::seeding_outgoing_connections) + || setting_changed(pack, m_settings, settings_pack::enable_outgoing_tcp) + || setting_changed(pack, m_settings, settings_pack::enable_outgoing_utp) + ; + +#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(); + } + else + { + reopen_listen_sockets(); + } + + if (update_want_peers) + { + for (auto const& t : m_torrents) + t->update_want_peers(); + } + } + + 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_context); + 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::utp; + udp::endpoint udp_bind_ep(bind_ep.address(), bind_ep.port()); + + ret->udp_sock = std::make_shared(m_io_context, 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, get_resolver() + , settings().get_bool(settings_pack::socks5_udp_send_local_ep)); + + ADD_OUTSTANDING_ASYNC("session_impl::on_udp_packet"); + ret->udp_sock->sock.async_read(aux::make_handler([this, ret](error_code const& e) + { this->on_udp_packet(ret->udp_sock, ret, ret->ssl, e); } + , 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) + { 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_context, 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_context, 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) + { +#if !TORRENT_USE_SSL + 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 + } + +#if defined TORRENT_ANDROID && __ANDROID_API__ >= 24 + // For Android API >= 24, enum_routes with the current NETLINK based + // implementation is unsupported (maybe in the future the operation + // will be restore using another implementation). If routes is empty, + // allow using unspecified address is a best effort approach that + // seems to work. The issue with this approach is with the DHTs, + // because for IPv6 this is not following BEP 32 and BEP 45. See: + // https://www.bittorrent.org/beps/bep_0032.html + // https://www.bittorrent.org/beps/bep_0045.html + if (!routes.empty()) expand_unspecified_address(ifs, routes, eps); +#else + expand_unspecified_address(ifs, routes, eps); +#endif + 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::utp; + + 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->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.emplace( + instantiate_connection(m_io_context, m_i2p_conn.proxy() + , nullptr, nullptr, true, false)); + + ADD_OUTSTANDING_ASYNC("session_impl::on_i2p_accept"); + auto& s = boost::get(*m_i2p_listen_socket); + 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, _1)); + } + + void session_impl::on_i2p_accept(error_code const& e) + { + COMPLETE_ASYNC("session_impl::on_i2p_accept"); + 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(std::move(*m_i2p_listen_socket)); + m_i2p_listen_socket.reset(); + } +#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_SSL_PEERS + 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_SSL_PEERS + (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_SSL_PEERS + ssl == transport::ssl ? m_ssl_utp_socket_manager : +#endif + m_utp_socket_manager; + + for (;;) + { + aux::array p; + error_code err; + int const num_packets = s->sock.read(p, err); + + for (udp_socket::packet& packet : span(p).first(num_packets)) + { + 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 + auto listen_socket = ls.lock(); + 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([this, socket, ls, ssl](error_code const& e) + { this->on_udp_packet(std::move(socket), std::move(ls), ssl, e); } + , s->udp_handler_storage, *this)); + } + + void session_impl::async_accept(std::shared_ptr const& listener + , transport const ssl) +#ifndef BOOST_NO_EXCEPTIONS + try +#endif + { + TORRENT_ASSERT(!m_abort); + + std::weak_ptr ls(listener); + m_stats_counters.inc_stats_counter(counters::num_outstanding_accept); + ADD_OUTSTANDING_ASYNC("session_impl::on_accept_connection"); + listener->async_accept([this, ls, ssl] (error_code const& ec, true_tcp_socket s) + { return wrap(&session_impl::on_accept_connection, std::move(s), ec, ls, ssl); }); + } +#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 + + void session_impl::on_accept_connection(true_tcp_socket s, error_code const& e + , std::weak_ptr listen_socket, 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() + , [](std::shared_ptr const& lhs, std::shared_ptr const& rhs) + { return lhs->num_peers() < rhs->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)->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()) + { + m_alerts.emplace_alert(ep.address().to_string() + , 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 listen sockets if we're + // using a proxy. We should only accept peers via the proxy, never + // directly. + // This path is only for accepting incoming TCP sockets. The udp_socket + // class also restricts incoming packets based on proxy settings. + if (m_settings.get_int(settings_pack::proxy_type) != settings_pack::none + && m_settings.get_bool(settings_pack::proxy_peer_connections)) + 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; + + socket_type c = [&]{ +#ifdef TORRENT_SSL_PEERS + 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 + return socket_type(ssl_stream(tcp::socket(std::move(s)), m_peer_ssl_ctx)); + } + else +#endif + { + return socket_type(tcp::socket(std::move(s))); + } + }(); + +#ifdef TORRENT_SSL_PEERS + TORRENT_ASSERT((ssl == transport::ssl) == is_ssl(c)); +#endif + +#ifdef TORRENT_SSL_PEERS + if (ssl == transport::ssl) + { + TORRENT_ASSERT(is_ssl(c)); + + // save the socket so we can cancel the handshake + // TODO: this size need to be capped + auto iter = m_incoming_sockets.emplace(std::make_unique(std::move(c))).first; + + auto sock = iter->get(); + // for SSL connections, incoming_connection() is called + // after the handshake is done + ADD_OUTSTANDING_ASYNC("session_impl::ssl_handshake"); + boost::get>(**iter).async_accept_handshake( + [this, sock] (error_code const& err) { ssl_handshake(err, sock); }); + } + else +#endif + { + incoming_connection(std::move(c)); + } + } + +#ifdef TORRENT_SSL_PEERS + + void session_impl::on_incoming_utp_ssl(socket_type s) + { + TORRENT_ASSERT(is_ssl(s)); + + // save the socket so we can cancel the handshake + + // TODO: this size need to be capped + auto iter = m_incoming_sockets.emplace(std::make_unique(std::move(s))).first; + auto sock = iter->get(); + + // for SSL connections, incoming_connection() is called + // after the handshake is done + ADD_OUTSTANDING_ASYNC("session_impl::ssl_handshake"); + boost::get>(**iter).async_accept_handshake( + [this, sock] (error_code const& err) { ssl_handshake(err, sock); }); + } + + // 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, socket_type* sock) + { + COMPLETE_ASYNC("session_impl::ssl_handshake"); + + auto iter = m_incoming_sockets.find(sock); + + // this happens if the SSL connection is aborted because we're shutting + // down + if (iter == m_incoming_sockets.end()) return; + + socket_type s(std::move(**iter)); + TORRENT_ASSERT(is_ssl(s)); + m_incoming_sockets.erase(iter); + + 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(), socket_type_name(s)); + } +#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(std::move(s)); + } + +#endif // TORRENT_SSL_PEERS + + void session_impl::incoming_connection(socket_type s) + { + TORRENT_ASSERT(is_single_thread()); + + if (m_abort) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log(" <== INCOMING CONNECTION [ ignored, aborting ]"); +#endif + return; + } + + 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) + && boost::get(&s)) + { +#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_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()) + { + session_log("<== INCOMING CONNECTION [ rejected, local interface has incoming connections disabled: %s ]" + , local.address().to_string().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()) + { + session_log("<== INCOMING CONNECTION [ rejected, not allowed local interface: %s ]" + , local.address().to_string().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(), socket_type_idx(s)); + 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, socket_type_idx(s) + , 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::shared_ptr const& i) + { return !i->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(socket_type_idx(s), endp); + + peer_connection_args pack{ + this + , &m_settings + , &m_stats_counters + , m_disk_thread.get() + , &m_io_context + , std::weak_ptr() + , std::move(s) + , endp + , nullptr + , aux::generate_peer_id(m_settings) + }; + + std::shared_ptr c + = std::make_shared(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->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()); + + 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) + { + post(m_io_context, make_handler([this] { abort_stage2(); } + , m_abort_handler_storage, *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_SSL_PEERS + && m_ssl_utp_socket_manager.num_sockets() == 0 +#endif + && m_undead_peers.empty() + && m_tracker_manager.empty()) + { + // this is where shutdown completes. We won't issue another + // on_tick() + 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_SSL_PEERS + , m_ssl_utp_socket_manager.num_sockets() +#else + , 0 +#endif + , int(m_undead_peers.size())); +#endif + } + + if (e && e != boost::asio::error::operation_aborted) + { +#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"); + milliseconds const tick_interval(m_abort ? 100 : m_settings.get_int(settings_pack::tick_interval)); + m_timer.expires_at(now + tick_interval); + m_timer.async_wait(aux::make_handler([this](error_code const& err) + { 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_SSL_PEERS + 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_SSL_PEERS + 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->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() > 1 + && p.m_channel_state[peer_connection::download_channel] & peer_info::bw_network) + ++num_peers[protocol][peer_connection::download_channel]; + if (!p.upload_queue().empty() + && p.m_channel_state[peer_connection::upload_channel] & peer_info::bw_network) + ++num_peers[protocol][peer_connection::upload_channel]; + } + + int const 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 uTP peers, don't throttle TCP + int const total_peers = num_peers[0][i] + num_peers[1][i]; + if (num_peers[1][i] == 0 || total_peers < 5) + { + set_rate_limit(m_tcp_peer_class, i, 0); + } + else + { + if (num_peers[0][i] == 0) num_peers[0][i] = 1; + // 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]; + int const limit = std::max(int(rate * num_peers[0][i] * 4 / total_peers), lower_limit[i]); + set_rate_limit(m_tcp_peer_class, i, limit); + } + } + } + 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) + { + int const limit = std::min(m_settings.get_int(settings_pack::connections_limit) + , std::numeric_limits::max() / 100); + int const cutoff = std::min(m_settings.get_int(settings_pack::peer_turnover_cutoff), 100); + if (num_connections() >= limit * 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() + , [] (std::shared_ptr const& lhs, std::shared_ptr const& rhs) + { return lhs->num_peers() < rhs->num_peers(); }); + + TORRENT_ASSERT(i != m_torrents.end()); + int const peers_to_disconnect = std::min(std::max( + (*i)->num_peers() * m_settings.get_int(settings_pack::peer_turnover) / 100, 1) + , (*i)->num_connect_candidates()); + (*i)->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& t : m_torrents) + { + // ths disconnect logic is disabled for torrents with + // too low connection limit + int const max = std::min(t->max_connections() + , std::numeric_limits::max() / 100); + if (t->num_peers() < max * cutoff / 100 || max < 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 if (m_dht_nodes.size() >= 200) + m_dht_nodes[random(std::uint32_t(m_dht_nodes.size() - 1))] = 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"); + m_dht_announce_timer.expires_after(seconds(0)); + m_dht_announce_timer.async_wait([this](error_code const& err) { + 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); + + update_dht_announce_interval(); + + 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.size()) + m_next_dht_torrent = 0; + m_torrents[m_next_dht_torrent]->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.size()) + m_next_dht_torrent = 0; + } +#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); + m_lsd_announce_timer.expires_after(seconds(delay)); + m_lsd_announce_timer.async_wait([this](error_code const& err) { + wrap(&session_impl::on_lsd_announce, err); }); + + if (m_torrents.empty()) return; + + if (m_next_lsd_torrent >= m_torrents.size()) + m_next_lsd_torrent = 0; + m_torrents[m_next_lsd_torrent]->lsd_announce(); + ++m_next_lsd_torrent; + if (m_next_lsd_torrent >= m_torrents.size()) + m_next_lsd_torrent = 0; + } + + 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* const 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(); + auto* const 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* const 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()); + } + + int const allowed_upload_slots = unchoke_sort(peers + , unchoke_interval, m_settings); + + if (m_settings.get_int(settings_pack::choking_algorithm) == settings_pack::fixed_slots_choker) + { + int const upload_slots = get_int_setting(settings_pack::unchoke_slots_limit); + m_stats_counters.set_value(counters::num_unchoke_slots, upload_slots); + } + else + { + 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(info_hash_t 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(info_hash_t const& info_hash) const + { + TORRENT_ASSERT(is_single_thread()); + + torrent* i = nullptr; + info_hash.for_each([&](sha1_hash const& ih, protocol_version) + { + if (i == nullptr) i = m_torrents.find(ih); + }); +#if TORRENT_USE_INVARIANT_CHECKS + for (auto const& te : m_torrents) + { + TORRENT_ASSERT(te); + } +#endif + if (i != nullptr) return i->shared_from_this(); + return std::weak_ptr(); + } + + void session_impl::insert_torrent(info_hash_t const& ih, std::shared_ptr const& t) + { + m_torrents.insert(ih, t); + t->added(); + } + + void session_impl::update_torrent_info_hash(std::shared_ptr const& t + , info_hash_t const& old_ih) + { + m_torrents.erase(old_ih); + m_torrents.insert(t->torrent_file().info_hashes(), t); + } + + 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; + + return m_torrents.find_obfuscated(obfuscated); + } +#endif + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + std::vector> session_impl::find_collection( + std::string const& collection) const + { + std::vector> ret; + for (auto const& t : m_torrents) + { + 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(std::shared_ptr const& lhs + , std::shared_ptr const& rhs) + { + // a torrent with 0 peers is never a good disconnect candidate + // since there's nothing to disconnect + if ((lhs->num_peers() == 0) != (rhs->num_peers() == 0)) + return lhs->num_peers() != 0; + + // other than that, always prefer to disconnect peers from seeding torrents + // in order to not harm downloading ones + if (lhs->is_seed() != rhs->is_seed()) + return lhs->is_seed(); + + return lhs->num_peers() > rhs->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; + } + +#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->is_aborted()) continue; + torrent_status st; + t->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() + { +#ifndef TORRENT_DISABLE_DHT + std::vector dht_stats; + if (m_dht) + dht_stats = m_dht->dht_status(); + + if (dht_stats.empty()) + { + // for backwards compatibility, still post an empty alert if we don't + // have any active DHT nodes + m_alerts.emplace_alert(std::vector{} + , std::vector{}, dht::node_id{}, udp::endpoint{}); + } + else + { + for (auto& s : dht_stats) + { + m_alerts.emplace_alert( + std::move(s.table), std::move(s.requests) + , s.our_id, s.local_endpoint); + } + } +#endif + } + + std::vector session_impl::get_torrents() const + { + std::vector ret; + + for (auto const& i : m_torrents) + { + if (i->is_aborted()) continue; + ret.push_back(torrent_handle(i)); + } + return ret; + } + + torrent_handle session_impl::find_torrent_handle(sha1_hash const& info_hash) + { + return torrent_handle(find_torrent(info_hash_t(info_hash))); + } + + void session_impl::async_add_torrent(add_torrent_params* params) + { + std::unique_ptr holder(params); + error_code ec; + add_torrent(std::move(*params), ec); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + void session_impl::add_extensions_to_torrent( + std::shared_ptr const& torrent_ptr, client_data_t const 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) + { + 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(); }); + +#ifndef TORRENT_DISABLE_EXTENSIONS + auto extensions = std::move(params.extensions); + auto const userdata = std::move(params.userdata); +#endif + + // copy the most important fields from params to pass back in the + // add_torrent_alert + add_torrent_params alert_params; + alert_params.flags = params.flags; + alert_params.ti = params.ti; + alert_params.name = params.name; + alert_params.save_path = params.save_path; + alert_params.userdata = params.userdata; + alert_params.trackerid = params.trackerid; + + auto const flags = params.flags; + + info_hash_t info_hash; + bool added; + std::tie(torrent_ptr, info_hash, added) = add_torrent_impl(std::move(params), ec); + + alert_params.info_hashes = info_hash; + + torrent_handle handle(torrent_ptr); + m_alerts.emplace_alert(handle, std::move(alert_params), ec); + + if (!torrent_ptr) return handle; + + TORRENT_ASSERT(info_hash.has_v1() || info_hash.has_v2()); + +#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 : extensions) + { + std::shared_ptr tp(ext(handle, userdata)); + if (tp) torrent_ptr->add_extension(std::move(tp)); + } + + add_extensions_to_torrent(torrent_ptr, userdata); +#endif + + TORRENT_ASSERT(info_hash == torrent_ptr->torrent_file().info_hashes()); + insert_torrent(info_hash, torrent_ptr); + + // 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 (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::tuple, info_hash_t, 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; + using ret_t = std::tuple, info_hash_t, bool>; + +#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 ret_t{ptr_t(), params.info_hashes, false}; + params.url.clear(); + } +#endif + + if (params.ti && !params.ti->is_valid()) + { + ec = errors::no_metadata; + return ret_t{ptr_t(), params.info_hashes, false}; + } + + if (params.ti && params.ti->is_valid() && params.ti->num_files() == 0) + { + ec = errors::no_files_in_torrent; + return ret_t{ptr_t(), params.info_hashes, false}; + } + + if (params.ti + && ((params.info_hashes.has_v1() && params.info_hashes.v1 != params.ti->info_hashes().v1) + || (params.info_hashes.has_v2() && params.info_hashes.v2 != params.ti->info_hashes().v2) + )) + { + ec = errors::mismatching_info_hash; + return ret_t{ptr_t(), params.info_hashes, 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); + + if (params.ti) + { + for (auto const& n : params.ti->nodes()) + add_dht_node_name(n); + } +#endif + + INVARIANT_CHECK; + + if (is_aborted()) + { + ec = errors::session_is_closing; + return ret_t{ptr_t(), params.info_hashes, false}; + } + + // figure out the info hash of the torrent and make sure + // params.info_hashes is set correctly + if (params.ti) + { + params.info_hashes = params.ti->info_hashes(); +#if TORRENT_ABI_VERSION < 3 + params.info_hash = params.info_hashes.get_best(); +#endif + } + + if (!params.info_hashes.has_v1() && !params.info_hashes.has_v2()) + { + ec = errors::missing_info_hash_in_uri; + return ret_t{ptr_t(), params.info_hashes, false}; + } + + // is the torrent already active? + std::shared_ptr torrent_ptr = find_torrent(params.info_hashes).lock(); + + if (torrent_ptr) + { + if (!(params.flags & torrent_flags::duplicate_is_error)) + return ret_t{std::move(torrent_ptr), params.info_hashes, false}; + + ec = errors::duplicate_torrent; + return ret_t{ptr_t(), params.info_hashes, 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); + } + + try + { + torrent_ptr = std::make_shared(*this, m_paused, std::move(params)); + torrent_ptr->set_queue_position(m_download_queue.end_index()); + } + catch (system_error const& e) + { + ec = e.code(); + return ret_t{ptr_t(), params.info_hashes, false}; + } + + // it's fine to copy this moved-from info_hash_t object, since its move + // construction is just a copy. + return ret_t{std::move(torrent_ptr), params.info_hashes, 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; +#if TORRENT_USE_SSL + if (boost::get>(&s) != nullptr) + { + impl = boost::get>(s).next_layer().get_impl(); + ssl = transport::ssl; + } + else +#endif + impl = boost::get(s).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) + { + impl->m_sock = 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_context, 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 (bind_ep.address().is_unspecified()) + { + 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_context, 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(), tptr->get_userdata()); + + remove_torrent_impl(tptr, options); + + tptr->abort(); + } + + void session_impl::remove_torrent_impl(std::shared_ptr tptr + , remove_flags_t const options) + { + m_torrents.erase(tptr->torrent_file().info_hashes()); + + torrent& t = *tptr; + 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_hashes()); + } + } + + tptr->update_gauge(); + tptr->removed(); + +#ifndef TORRENT_DISABLE_DHT + if (m_next_dht_torrent == m_torrents.size()) + m_next_dht_torrent = 0; +#endif + if (m_next_lsd_torrent == m_torrents.size()) + m_next_lsd_torrent = 0; + + // this torrent may open up a slot for a queued torrent + trigger_auto_manage(); + } + +#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->port_filter_updated(); + } + else + { + m_port_filter.add_rule(0, 1024, 0); + } + } + + void session_impl::update_auto_sequential() + { + for (auto& i : m_torrents) + i->update_auto_sequential(); + } + + void session_impl::update_max_failcount() + { + for (auto& i : m_torrents) + i->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, get_resolver() + , settings().get_bool(settings_pack::socks5_udp_send_local_ep)); + } + + 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_count_slow() + { + error_code ec; + for (auto const& tp : m_torrents) + { + tp->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_SSL_PEERS + 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_SSL_PEERS + 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 + && m_settings.get_bool(settings_pack::proxy_peer_connections)) + 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(info_hash_t(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; + + protocol_version const v = ih == t->torrent_file().info_hashes().v1 + ? protocol_version::V1 : protocol_version::V2; + + t->add_peer(peer, peer_info::lsd, v == protocol_version::V2 ? pex_lt_v2 : pex_flags_t(0)); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + t->debug_log("lsd add_peer() [ %s ]" + , peer.address().to_string().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(std::shared_ptr const& 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_context, *this, listen_socket_handle(s)); + 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); + } + } + + void session_impl::on_port_mapping(port_mapping_t const mapping + , address const& external_ip, int port + , portmap_protocol const proto, error_code const& ec + , portmap_transport const transport + , listen_socket_handle const& ls) + { + TORRENT_ASSERT(is_single_thread()); + + listen_socket_t* listen_socket = ls.get(); + + // 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, listen_socket ? listen_socket->local_endpoint.address() : address()); + } + + if (!listen_socket) return; + + if (!ec && !external_ip.is_unspecified()) + { + // 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 + listen_socket->external_address.cast_vote(external_ip, source_router, address()); + } + + if (proto == portmap_protocol::tcp) listen_socket->tcp_port_mapping[transport].port = port; + else if (proto == portmap_protocol::udp) listen_socket->udp_port_mapping[transport].port = port; + + if (!ec && m_alerts.should_post()) + { + m_alerts.emplace_alert(mapping, port + , transport, proto, listen_socket->local_endpoint.address()); + } + } + +#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::shared_ptr const& t) + { return acc + t->num_known_peers(); }); + + return s; + } +#endif // TORRENT_ABI_VERSION + +#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_settings); + m_dht = std::make_shared( + static_cast(this) + , m_io_context + , [this](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_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(); + } + +#if TORRENT_ABI_VERSION <= 2 + void session_impl::set_dht_settings(dht::dht_settings const& settings) + { +#ifndef TORRENT_DISABLE_DHT +#define SET_BOOL(name) m_settings.set_bool(settings_pack::dht_ ## name, settings.name) +#define SET_INT(name) m_settings.set_int(settings_pack::dht_ ## name, settings.name) + + SET_INT(max_peers_reply); + SET_INT(search_branching); + SET_INT(max_fail_count); + SET_INT(max_torrents); + SET_INT(max_dht_items); + SET_INT(max_peers); + SET_INT(max_torrent_search_reply); + SET_BOOL(restrict_routing_ips); + SET_BOOL(restrict_search_ips); + SET_BOOL(extended_routing_table); + SET_BOOL(aggressive_lookups); + SET_BOOL(privacy_lookups); + SET_BOOL(enforce_node_id); + SET_BOOL(ignore_dark_internet); + SET_INT(block_timeout); + SET_INT(block_ratelimit); + SET_BOOL(read_only); + SET_INT(item_lifetime); + SET_INT(upload_rate_limit); + SET_INT(sample_infohashes_interval); + SET_INT(max_infohashes_sample_count); +#undef SET_BOOL +#undef SET_INT + update_dht_upload_rate_limit(); +#endif + } + + dht::dht_settings session_impl::get_dht_settings() const + { + dht::dht_settings sett; +#ifndef TORRENT_DISABLE_DHT +#define SET_BOOL(name) \ + sett.name = m_settings.get_bool( settings_pack::dht_ ## name ) +#define SET_INT(name) \ + sett.name = m_settings.get_int( settings_pack::dht_ ## name ) + + SET_INT(max_peers_reply); + SET_INT(search_branching); + SET_INT(max_fail_count); + SET_INT(max_torrents); + SET_INT(max_dht_items); + SET_INT(max_peers); + SET_INT(max_torrent_search_reply); + SET_BOOL(restrict_routing_ips); + SET_BOOL(restrict_search_ips); + SET_BOOL(extended_routing_table); + SET_BOOL(aggressive_lookups); + SET_BOOL(privacy_lookups); + SET_BOOL(enforce_node_id); + SET_BOOL(ignore_dark_internet); + SET_INT(block_timeout); + SET_INT(block_ratelimit); + SET_BOOL(read_only); + SET_INT(item_lifetime); + SET_INT(upload_rate_limit); + SET_INT(sample_infohashes_interval); + SET_INT(max_infohashes_sample_count); +#undef SET_BOOL +#undef SET_INT +#endif + return sett; + } +#endif + + 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(aux::alert_manager& alerts, sha1_hash target, int num) + { + if (alerts.should_post()) + alerts.emplace_alert(target, num); + } + + void on_dht_put_mutable_item(aux::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(aux::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(aux::alert_manager& alerts, client_data_t 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](sha1_hash const& nid + , time_duration const interval + , int const num, std::vector samples + , std::vector> nodes) + { + m_alerts.emplace_alert(nid + , ep, interval, num, std::move(samples), std::move(nodes)); + }); + } + + void session_impl::dht_direct_request(udp::endpoint const& ep, entry& e, client_data_t userdata) + { + if (!m_dht) return; + m_dht->direct_request(ep, e, std::bind(&on_direct_response, std::ref(m_alerts), userdata, _1)); + } + +#endif + + 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_context run loop with an exception + m_connections.clear(); + for (auto& t : m_torrents) + { + t->panic(); + t->abort(); + } + m_torrents.clear(); + + // this has probably been called already, but in case of sudden + // termination through an exception, it may not have been done + abort_stage2(); + +#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 + + + // 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_traffic_class(*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_traffic_class(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 applying 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_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 + if (m_settings.get_int(settings_pack::dht_upload_rate_limit) > std::numeric_limits::max() / 3) + { + m_settings.set_int(settings_pack::dht_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 (m_settings.get_int(settings_pack::hashing_threads) < 0) + m_settings.set_int(settings_pack::hashing_threads, 0); + } + + 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; + + post(m_io_context, [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()) + { + session_log("listen socket buffer size [ udp %s:%d ] %s" + , l->udp_sock->sock.local_endpoint().address().to_string().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()) + { + session_log("listen socket buffer size [ tcp %s:%d] %s" + , l->sock->local_endpoint().address().to_string().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"); + 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); + } + + m_dht_announce_timer.expires_after(seconds(delay)); + m_dht_announce_timer.async_wait([this](error_code const& e) { + 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->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->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->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() + { +#if TORRENT_USE_SSL + auto const flags = m_settings.get_bool(settings_pack::validate_https_trackers) + ? ssl::context::verify_peer + | ssl::context::verify_fail_if_no_peer_cert + | ssl::context::verify_client_once + : ssl::context::verify_none; + error_code ec; + m_ssl_ctx.set_verify_mode(flags, ec); + +#ifndef TORRENT_DISABLE_LOGGING + if (ec) session_log("SSL set_verify_mode failed: %s", ec.message().c_str()); +#endif +#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_context); + m_ip_notifier->async_wait([this](error_code const& e) + { 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_context, *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->local_endpoint.address()); + 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 const& s : m_listen_sockets) + { + start_upnp(s); + remap_ports(remap_upnp, *s); + } + } + + void session_impl::start_upnp(std::shared_ptr const& 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_context, m_settings + , *this, s->local_endpoint.address().to_v4(), s->netmask.to_v4(), s->device + , listen_socket_handle(s)); + 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 + , listen_socket_handle const& ls) const + { + listen_socket_t const* listen_socket = ls.get(); + if (m_alerts.should_post()) + m_alerts.emplace_alert(transport, msg + , listen_socket ? listen_socket->local_endpoint.address() : address()); + } + + 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->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; + 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); + } + } +#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, operation_t const op, std::string const& str + , seconds32 const retry_interval) + { + TORRENT_UNUSED(retry_interval); + debug_log("*** tracker error: [%s] %s %s" + , operation_name(op), 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_params.cpp b/src/session_params.cpp new file mode 100644 index 0000000..2a201d2 --- /dev/null +++ b/src/session_params.cpp @@ -0,0 +1,295 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2019-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/session_params.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/extensions/ut_pex.hpp" +#include "libtorrent/extensions/ut_metadata.hpp" +#include "libtorrent/extensions/smart_ban.hpp" + +namespace libtorrent { + +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 +} + +} // anonymous namespace + +TORRENT_VERSION_NAMESPACE_3 + +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 // NOLINT + , std::vector> exts) + : settings(sp) + , extensions(std::move(exts)) +#ifndef TORRENT_DISABLE_DHT + , dht_storage_constructor(dht::dht_default_storage_constructor) +#endif + {} + +session_params::session_params(session_params const&) = default; +session_params::session_params(session_params&&) = default; +session_params::~session_params() = default; + +session_params& session_params::operator=(session_params const&) & = default; +session_params& session_params::operator=(session_params&&) & = default; + +TORRENT_VERSION_NAMESPACE_3_END + +session_params read_session_params(bdecode_node const& e, save_state_flags_t const flags) +{ + session_params params; + + if (e.type() != bdecode_node::dict_t) return params; + +#ifndef TORRENT_DISABLE_DHT +#if TORRENT_ABI_VERSION <= 2 + if (flags & session_handle::save_dht_settings) +#else + if (flags & session_handle::save_settings) +#endif + { + bdecode_node settings = e.dict_find_dict("dht"); + if (settings) + { + aux::apply_deprecated_dht_settings(params.settings, settings); +#if TORRENT_ABI_VERSION <= 2 + params.dht_settings = dht::read_dht_settings(settings); +#endif + } + } +#endif + + if (flags & session_handle::save_settings) + { + bdecode_node 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_state) + { + bdecode_node settings = e.dict_find_dict("dht state"); + if (settings) + { + params.dht_state = dht::read_dht_state(settings); + } + } +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + if (flags & session::save_extension_state) + { + auto ext = e.dict_find_dict("extensions"); + if (ext) + { + for (int i = 0; i < ext.dict_size(); ++i) + { + bdecode_node val; + string_view key; + std::tie(key, val) = ext.dict_at(i); + if (val.type() != bdecode_node::string_t) continue; + params.ext_state[std::string(key)] = std::string(val.string_value()); + } + } + } +#endif + + if (flags & session_handle::save_ip_filter) + { + auto const v4 = e.dict_find_list("ip_filter4"); + ip_filter load; + if (v4) + { + int const count = v4.list_size(); + for (int i = 0; i < count; ++i) + { + auto const str = v4.list_string_value_at(i); + if (str.size() < 4 + 4 + 4) continue; + char const* ptr = str.data(); + auto const first = aux::read_v4_address(ptr); + auto const last = aux::read_v4_address(ptr); + auto const f = aux::read_uint32(ptr); + // ignore invalid entries + if (first > last) continue; + load.add_rule(first, last, f); + } + } + + auto const v6 = e.dict_find_list("ip_filter6"); + if (v6) + { + int const count = v6.list_size(); + for (int i = 0; i < count; ++i) + { + auto const str = v6.list_string_value_at(i); + if (str.size() < 16 + 16 + 4) continue; + char const* ptr = str.data(); + auto const first = aux::read_v6_address(ptr); + auto const last = aux::read_v6_address(ptr); + auto const f = aux::read_uint32(ptr); + // ignore invalid entries + if (first > last) continue; + load.add_rule(first, last, f); + } + } + + if (!load.empty()) + { + params.ip_filter = std::move(load); + } + } + + return params; +} + +session_params read_session_params(span buf + , save_state_flags_t const flags) +{ + return read_session_params(bdecode(buf), flags); +} + +entry write_session_params(session_params const& sp, save_state_flags_t const flags) +{ + entry e; + +#ifndef TORRENT_DISABLE_DHT +#if TORRENT_ABI_VERSION <= 2 + if (flags & session_handle::save_dht_settings) + { + e["dht"] = dht::save_dht_settings(sp.dht_settings); + } +#endif + + if (flags & session::save_dht_state) + { + e["dht state"] = dht::save_dht_state(sp.dht_state); + } +#endif + + if (flags & session_handle::save_settings) + { + save_settings_to_dict(sp.settings, e["settings"].dict()); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + if (flags & session::save_extension_state) + { + auto& ext = e["extensions"].dict(); + for (auto const& val : sp.ext_state) ext[val.first] = val.second; + } +#endif + + if (flags & session_handle::save_ip_filter) + { + std::vector> v4; + std::vector> v6; + std::tie(v4, v6) = sp.ip_filter.export_filter(); + if (!v4.empty()) + { + auto& v4_list = e["ip_filter4"].list(); + for (auto const& ent : v4) + { + v4_list.emplace_back(); + auto ptr = std::back_inserter(v4_list.back().string()); + aux::write_address(ent.first, ptr); + aux::write_address(ent.last, ptr); + aux::write_uint32(ent.flags, ptr); + } + } + if (!v6.empty()) + { + auto& v6_list = e["ip_filter6"].list(); + for (auto const& ent : v6) + { + v6_list.emplace_back(); + auto ptr = std::back_inserter(v6_list.back().string()); + aux::write_address(ent.first, ptr); + aux::write_address(ent.last, ptr); + aux::write_uint32(ent.flags, ptr); + } + } + } + + return e; +} + +std::vector write_session_params_buf(session_params const& sp, save_state_flags_t const flags) +{ + auto const e = write_session_params(sp, flags); + std::vector ret; + bencode(std::back_inserter(ret), e); + return ret; +} + +} + diff --git a/src/session_settings.cpp b/src/session_settings.cpp new file mode 100644 index 0000000..63805f6 --- /dev/null +++ b/src/session_settings.cpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2007, 2015, 2017, 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 "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..ed81c0f --- /dev/null +++ b/src/session_stats.cpp @@ -0,0 +1,599 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2019, Steven Siloti +Copyright (c) 2020, FranciscoPombal +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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) + + // 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) + METRIC(ses, num_outgoing_hash_request) + METRIC(ses, num_outgoing_hashes) + METRIC(ses, num_outgoing_hash_reject) + + // 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) + + // 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) + + 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. + 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) + + // the number of blocks written and read from disk in total. A block is 16 + // kiB. ``num_blocks_written`` and ``num_blocks_read`` + 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 disk I/O operation for reads and writes. One disk + // operation may transfer more then one block. + 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_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) + + // The number of times a lost packet has been interpreted as congestion, + // cutting the congestion window in half. Some lost packets are not + // interpreted as congestion, notably MTU-probes + METRIC(utp, utp_packet_loss) + + // The number of timeouts experienced. This is when a connection doesn't + // hear back from the other end within a sliding average RTT + 2 average + // deviations from the mean (approximately). The actual time out is + // configurable and also depends on the state of the socket. + METRIC(utp, utp_timeout) + + // The total number of packets sent and received + METRIC(utp, utp_packets_in) + METRIC(utp, utp_packets_out) + + // The number of packets lost but re-sent by the fast-retransmit logic. + // This logic is triggered after 3 duplicate ACKs. + METRIC(utp, utp_fast_retransmit) + + // The number of packets that were re-sent, for whatever reason + METRIC(utp, utp_packet_resend) + + // The number of incoming packets where the delay samples were above + // and below the delay target, respectively. The delay target is + // configurable and is a parameter to the LEDBAT congestion control. + METRIC(utp, utp_samples_above_target) + METRIC(utp, utp_samples_below_target) + + // The total number of packets carrying payload received and sent, + // respectively. + METRIC(utp, utp_payload_pkts_in) + METRIC(utp, utp_payload_pkts_out) + + // The number of packets received that are not valid uTP packets (but + // were sufficiently similar to not be treated as DHT or UDP tracker + // packets). + METRIC(utp, utp_invalid_pkts_in) + + // The number of duplicate payload packets received. This may happen if + // the outgoing ACK is lost. + 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..f0ab8e9 --- /dev/null +++ b/src/settings_pack.cpp @@ -0,0 +1,848 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 } +#define DEPRECATED2_SET(name, default_value, fun) { #name, fun, default_value } +#elif TORRENT_ABI_VERSION == 2 +#define DEPRECATED_SET(name, default_value, fun) { "", nullptr, 0 } +#define DEPRECATED_SET_STR(name, default_value, fun) { "", nullptr, nullptr } +#define DEPRECATED2_SET(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 } +#define DEPRECATED2_SET(name, default_value, fun) { "", nullptr, 0 } +#endif + +#ifdef TORRENT_WINDOWS +constexpr int CLOSE_FILE_INTERVAL = 240; +#else +constexpr int CLOSE_FILE_INTERVAL = 0; +#endif + +#ifdef TORRENT_WINDOWS +constexpr int DISK_WRITE_MODE = settings_pack::write_through; +#else +constexpr int DISK_WRITE_MODE = settings_pack::enable_os_cache; +#endif + + // tested to fail with _MSC_VER <= 1916. The actual version condition +#if !defined _MSC_VER +#define CONSTEXPR_SETTINGS constexpr +#else +#define CONSTEXPR_SETTINGS +#endif + + namespace { + + using aux::session_impl; + + CONSTEXPR_SETTINGS + 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, "-LT2060-", nullptr), + SET(dht_bootstrap_nodes, "dht.libtorrent.org:25401", &session_impl::update_dht_bootstrap_nodes) + }}); + + CONSTEXPR_SETTINGS + 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), + DEPRECATED_SET(use_read_cache, true, nullptr), + DEPRECATED_SET(use_write_cache, true, nullptr), + DEPRECATED_SET(dont_flush_write_cache, false, nullptr), + DEPRECATED_SET(coalesce_reads, false, nullptr), + DEPRECATED_SET(coalesce_writes, false, nullptr), + 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), + DEPRECATED2_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), + DEPRECATED_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), + DEPRECATED2_SET(allow_partial_disk_writes, true, nullptr), + DEPRECATED_SET(force_proxy, false, nullptr), + SET(support_share_mode, true, nullptr), + DEPRECATED2_SET(support_merkle_torrents, false, 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, nullptr), + SET(dht_restrict_routing_ips, true, nullptr), + SET(dht_restrict_search_ips, true, nullptr), + SET(dht_extended_routing_table, true, nullptr), + SET(dht_aggressive_lookups, true, nullptr), + SET(dht_privacy_lookups, false, nullptr), + SET(dht_enforce_node_id, false, nullptr), + SET(dht_ignore_dark_internet, true, nullptr), + SET(dht_read_only, false, nullptr), + SET(piece_extent_affinity, false, nullptr), + SET(validate_https_trackers, true, &session_impl::update_validate_https), + SET(ssrf_mitigation, true, nullptr), + SET(allow_idna, false, nullptr), + SET(enable_set_file_valid_data, false, nullptr), + SET(socks5_udp_send_local_ep, false, nullptr), + }}); + + CONSTEXPR_SETTINGS + 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, 2000, 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, nullptr), + 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), + DEPRECATED_SET(cache_size, 2048, nullptr), + DEPRECATED_SET(cache_buffer_chunk_size, 0, nullptr), + DEPRECATED_SET(cache_expiry, 300, nullptr), + SET(disk_io_write_mode, DISK_WRITE_MODE, 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, 0x04, &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), + DEPRECATED2_SET(read_cache_line_size, 32, nullptr), + DEPRECATED2_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), + DEPRECATED_SET(default_est_reciprocation_rate, 16000, nullptr), + DEPRECATED_SET(increase_est_reciprocation_rate, 20, nullptr), + DEPRECATED_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), + DEPRECATED_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, 2000, &session_impl::update_alert_queue_size), + SET(max_metadata_size, 3 * 1024 * 10240, nullptr), + SET(hashing_threads, 2, &session_impl::update_disk_threads), + SET(checking_mem_usage, 256, nullptr), + SET(predictive_piece_announce, 0, nullptr), + SET(aio_threads, 10, &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), + DEPRECATED_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), + SET(dht_max_peers_reply, 100, nullptr), + SET(dht_search_branching, 5, nullptr), + SET(dht_max_fail_count, 20, nullptr), + SET(dht_max_torrents, 2000, nullptr), + SET(dht_max_dht_items, 700, nullptr), + SET(dht_max_peers, 500, nullptr), + SET(dht_max_torrent_search_reply, 20, nullptr), + SET(dht_block_timeout, 5 * 60, nullptr), + SET(dht_block_ratelimit, 5, nullptr), + SET(dht_item_lifetime, 0, nullptr), + SET(dht_sample_infohashes_interval, 21600, nullptr), + SET(dht_max_infohashes_sample_count, 20, nullptr), + SET(max_piece_count, 0x200000, nullptr), + SET(metadata_token_limit, 2500000, nullptr), + }}); + +#undef SET +#undef DEPRECATED_SET +#undef CONSTEXPR_SETTINGS + + } // 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; + } + + settings_pack non_default_settings(aux::session_settings const& sett) + { + settings_pack ret; + sett.bulk_get([&ret](aux::session_settings_single_thread const& s) + { + // loop over all settings that differ from default + for (std::uint16_t i = 0; i < settings_pack::num_string_settings; ++i) + { + std::uint16_t const n = i | settings_pack::string_type_base; + if (ensure_string(str_settings[i].default_value) == s.get_str(n)) continue; + ret.set_str(n, s.get_str(n)); + } + + for (std::uint16_t i = 0; i < settings_pack::num_int_settings; ++i) + { + std::uint16_t const n = i | settings_pack::int_type_base; + if (int_settings[i].default_value == s.get_int(n)) continue; + ret.set_int(n, s.get_int(n)); + } + + for (std::uint16_t i = 0; i < settings_pack::num_bool_settings; ++i) + { + std::uint16_t const n = i | settings_pack::bool_type_base; + if (bool_settings[i].default_value == s.get_bool(n)) continue; + ret.set_bool(n, s.get_bool(n)); + } + }); + return ret; + } + + void save_settings_to_dict(settings_pack const& sett, entry::dictionary_type& out) + { + struct visitor + { + void operator()(std::uint16_t i, std::string const& str) { out[str_settings[i & settings_pack::index_mask].name] = str; } + void operator()(std::uint16_t i, int val) { out[int_settings[i & settings_pack::index_mask].name] = val; } + void operator()(std::uint16_t i, bool val) { out[bool_settings[i & settings_pack::index_mask].name] = val; } + entry::dictionary_type& out; + }; + sett.for_each(visitor{out}); + } + + 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; + } + + 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_PRECOND((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_PRECOND((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_PRECOND((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..ec314b9 --- /dev/null +++ b/src/sha1.cpp @@ -0,0 +1,329 @@ +/* +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_CNG \ + && !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); + } +} + +} // namespace libtorrent + +#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..00ad3c3 --- /dev/null +++ b/src/sha1_hash.cpp @@ -0,0 +1,179 @@ +/* + +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2016-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +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/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 + template + void digest32::stream_out(std::ostream& os) const + { + os << aux::to_hex(*this); + } + + // read hexadecimal digits from an istream into a digest32 + template + void digest32::stream_in(std::istream& is) + { + char hex[size() * 2]; + is.read(hex, size() * 2); + if (!aux::from_hex(hex, data())) + is.setstate(std::ios_base::failbit); + } + + // explicitly instantiate these template functions for sha1 and sha256 + template TORRENT_EXPORT void digest32<160>::stream_out(std::ostream&) const; + template TORRENT_EXPORT void digest32<256>::stream_out(std::ostream&) const; + + template TORRENT_EXPORT void digest32<160>::stream_in(std::istream&); + template TORRENT_EXPORT void digest32<256>::stream_in(std::istream&); + +#endif // TORRENT_USE_IOSTREAM + +namespace { + + void bits_shift_left(span const number, int n) noexcept + { + 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) noexcept + { + 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]); + } + } +} + + // shift left ``n`` bits. + template + digest32& digest32::operator<<=(int const n) & noexcept + { + bits_shift_left(m_number, n); + return *this; + } + + // shift right ``n`` bits. + template + digest32& digest32::operator>>=(int const n) & noexcept + { + bits_shift_right(m_number, n); + return *this; + } + + template TORRENT_EXPORT digest32<160>& digest32<160>::operator<<=(int) & noexcept; + template TORRENT_EXPORT digest32<256>& digest32<256>::operator<<=(int) & noexcept; + template TORRENT_EXPORT digest32<160>& digest32<160>::operator>>=(int) & noexcept; + template TORRENT_EXPORT digest32<256>& digest32<256>::operator>>=(int) & noexcept; + + 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/sha256.cpp b/src/sha256.cpp new file mode 100644 index 0000000..a7363f6 --- /dev/null +++ b/src/sha256.cpp @@ -0,0 +1,180 @@ +// SHA-256. Adapted from LibTomCrypt. This code is Public Domain +#include "libtorrent/sha256.hpp" + +#if !defined TORRENT_USE_LIBGCRYPT \ + && !TORRENT_USE_COMMONCRYPTO \ + && !TORRENT_USE_CNG \ + && !TORRENT_USE_CRYPTOAPI_SHA_512 \ + && !defined TORRENT_USE_LIBCRYPTO + +#include + +namespace libtorrent { namespace { + + using u32 = std::uint32_t; + using u64 = std::uint64_t; + + const u32 K[64] = + { + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL, + 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL, + 0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, + 0xc19bf174UL, 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, + 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, 0x983e5152UL, + 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL, + 0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, + 0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, + 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, + 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, 0x19a4c116UL, 0x1e376c08UL, + 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, + 0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, + 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL + }; + + u32 min(u32 x, u32 y) + { + return x < y ? x : y; + } + + u32 load32(const unsigned char* y) + { + return (u32(y[0]) << 24) | (u32(y[1]) << 16) | (u32(y[2]) << 8) | (u32(y[3]) << 0); + } + + void store64(u64 x, unsigned char* y) + { + for (int i = 0; i != 8; ++i) + y[i] = (x >> ((7 - i) * 8)) & 255; + } + + void store32(u32 x, unsigned char* y) + { + for (int i = 0; i != 4; ++i) + y[i] = (x >> ((3 - i) * 8)) & 255; + } + + u32 Ch(u32 x, u32 y, u32 z) { return z ^ (x & (y ^ z)); } + u32 Maj(u32 x, u32 y, u32 z) { return ((x | y) & z) | (x & y); } + u32 Rot(u32 x, u32 n) { return (x >> (n & 31)) | (x << (32 - (n & 31))); } + u32 Sh(u32 x, u32 n) { return x >> n; } + u32 Sigma0(u32 x) { return Rot(x, 2) ^ Rot(x, 13) ^ Rot(x, 22); } + u32 Sigma1(u32 x) { return Rot(x, 6) ^ Rot(x, 11) ^ Rot(x, 25); } + u32 Gamma0(u32 x) { return Rot(x, 7) ^ Rot(x, 18) ^ Sh(x, 3); } + u32 Gamma1(u32 x) { return Rot(x, 17) ^ Rot(x, 19) ^ Sh(x, 10); } + + void sha_compress(sha256_ctx& md, const unsigned char* buf) + { + u32 S[8], W[64], t0, t1, t; + + // Copy state into S + for (int i = 0; i < 8; i++) + S[i] = md.state[i]; + + // Copy the state into 512-bits into W[0..15] + for (int i = 0; i < 16; i++) + W[i] = load32(buf + (4 * i)); + + // Fill W[16..63] + for (int i = 16; i < 64; i++) + W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]; + + // Compress + auto RND = [&](u32 a, u32 b, u32 c, u32& d, u32 e, u32 f, u32 g, u32& h, u32 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 (unsigned i = 0; i < 64; ++i) + { + RND(S[0], S[1], S[2], S[3], S[4], S[5], S[6], S[7], i); + t = S[7]; S[7] = S[6]; S[6] = S[5]; S[5] = S[4]; + S[4] = S[3]; S[3] = S[2]; S[2] = S[1]; S[1] = S[0]; S[0] = t; + } + + // Feedback + for (int i = 0; i < 8; i++) + md.state[i] = md.state[i] + S[i]; + } +} // namespace + + void SHA256_init(sha256_ctx& md) + { + md.curlen = 0; + md.length = 0; + md.state[0] = 0x6A09E667UL; + md.state[1] = 0xBB67AE85UL; + md.state[2] = 0x3C6EF372UL; + md.state[3] = 0xA54FF53AUL; + md.state[4] = 0x510E527FUL; + md.state[5] = 0x9B05688CUL; + md.state[6] = 0x1F83D9ABUL; + md.state[7] = 0x5BE0CD19UL; + } + + void SHA256_update(sha256_ctx& md, std::uint8_t const* in, size_t len) + { + constexpr u32 block_size = u32(sizeof(sha256_ctx::buf)); + + while (len > 0) + { + if (md.curlen == 0 && len >= block_size) + { + sha_compress(md, in); + md.length += block_size * 8; + in += block_size; + len -= block_size; + } + else + { + u32 n = min(u32(len), block_size - md.curlen); + std::memcpy(md.buf + md.curlen, in, n); + md.curlen += n; + in += n; + len -= n; + + if (md.curlen == block_size) + { + sha_compress(md, md.buf); + md.length += 8 * block_size; + md.curlen = 0; + } + } + } + } + + void SHA256_final(std::uint8_t* digest, sha256_ctx& md) + { + // Increase the length of the message + md.length += md.curlen * 8; + + // Append the '1' bit + md.buf[md.curlen++] = std::uint8_t(0x80U); + + // If the length is currently above 56 bytes we append zeros then compress. + // Then we can fall back to padding zeros and length encoding like normal. + if (md.curlen > 56) + { + while (md.curlen < 64) + md.buf[md.curlen++] = 0; + sha_compress(md, md.buf); + md.curlen = 0; + } + + // Pad upto 56 bytes of zeroes + while (md.curlen < 56) + md.buf[md.curlen++] = 0; + + // Store length + store64(md.length, md.buf + 56); + sha_compress(md, md.buf); + + // Copy output + for (int i = 0; i < 8; i++) + store32(md.state[i], digest + (4 * i)); + } +} + +#endif diff --git a/src/smart_ban.cpp b/src/smart_ban.cpp new file mode 100644 index 0000000..cfb589b --- /dev/null +++ b/src/smart_ban.cpp @@ -0,0 +1,329 @@ +/* + +Copyright (c) 2007-2012, 2014-2019, Arvid Norberg +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/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 { + +struct torrent; + +namespace { + + + struct smart_ban_plugin final + : torrent_plugin + , std::enable_shared_from_this + { + explicit smart_ban_plugin(torrent& t) + : m_torrent(t) + {} + + 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)); + 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 const downloaders + = m_torrent.picker().get_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) + , 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 + , storage_error const& error) + { + TORRENT_ASSERT(m_torrent.session().is_single_thread()); + + // ignore read errors + if (error) return; + + hasher h; + h.update({buffer.data(), block_size}); + + 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 + , storage_error const& error) + { + TORRENT_ASSERT(m_torrent.session().is_single_thread()); + + // ignore read errors + if (error) return; + + hasher h; + h.update({buffer.data(), block_size}); + 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; + + // 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, client_data_t) + { + 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..20090af --- /dev/null +++ b/src/socket_io.cpp @@ -0,0 +1,167 @@ +/* + +Copyright (c) 2009-2010, 2013-2014, 2017, 2019, Arvid Norberg +Copyright (c) 2016, 2018, 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 "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) + { + return addr.to_string(); + } + + std::string address_to_bytes(address const& a) + { + std::string ret; + std::back_insert_iterator out(ret); + aux::write_address(a, out); + return ret; + } + + std::string endpoint_to_bytes(udp::endpoint const& ep) + { + std::string ret; + std::back_insert_iterator out(ret); + aux::write_endpoint(ep, out); + return ret; + } + + std::string print_endpoint(address const& addr, int port) + { + char buf[200]; + if (addr.is_v6()) + std::snprintf(buf, sizeof(buf), "[%s]:%d", addr.to_string().c_str(), port); + else + std::snprintf(buf, sizeof(buf), "%s:%d", addr.to_string().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..0972170 --- /dev/null +++ b/src/socket_type.cpp @@ -0,0 +1,269 @@ +/* + +Copyright (c) 2009, 2012-2015, 2017-2020, Arvid Norberg +Copyright (c) 2016, 2020, Steven Siloti +Copyright (c) 2016, 2020, Alden Torres +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_/array.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/ssl.hpp" +#include "libtorrent/debug.hpp" + +namespace libtorrent { + + char const* socket_type_name(socket_type_t const s) + { + static aux::array const names{{{ + "TCP", + "Socks5", + "HTTP", + "uTP", +#if TORRENT_USE_I2P + "I2P", +#else + "", +#endif +#if TORRENT_USE_SSL + "SSL/TCP", + "SSL/Socks5", + "SSL/HTTP", + "SSL/uTP" +#else + "","","","" +#endif + }}}; + return names[s]; + } + +namespace aux { + + struct is_ssl_visitor { +#if TORRENT_USE_SSL + template + bool operator()(ssl_stream const&) const { return true; } +#endif + template + bool operator()(T const&) const { return false; } + }; + + bool is_ssl(socket_type const& s) + { + return boost::apply_visitor(is_ssl_visitor{}, s); + } + + bool is_utp(socket_type const& s) + { + return boost::get(&s) +#if TORRENT_USE_SSL + || boost::get>(&s) +#endif + ; + } + +#if TORRENT_USE_I2P + bool is_i2p(socket_type const& s) + { + return boost::get(&s); + } +#endif + + struct idx_visitor { + socket_type_t operator()(tcp::socket const&) { return socket_type_t::tcp; } + socket_type_t operator()(socks5_stream const&) { return socket_type_t::socks5; } + socket_type_t operator()(http_stream const&) { return socket_type_t::http; } + socket_type_t operator()(utp_stream const&) { return socket_type_t::utp; } +#if TORRENT_USE_I2P + socket_type_t operator()(i2p_stream const&) { return socket_type_t::i2p; } +#endif +#if TORRENT_USE_SSL + socket_type_t operator()(ssl_stream const&) { return socket_type_t::tcp_ssl; } + socket_type_t operator()(ssl_stream const&) { return socket_type_t::socks5_ssl; } + socket_type_t operator()(ssl_stream const&) { return socket_type_t::http_ssl; } + socket_type_t operator()(ssl_stream const&) { return socket_type_t::utp_ssl; } +#endif + }; + + socket_type_t socket_type_idx(socket_type const& s) + { + return boost::apply_visitor(idx_visitor{}, s); + } + + char const* socket_type_name(socket_type const& s) + { + return socket_type_name(socket_type_idx(s)); + } + + struct set_close_reason_visitor { + close_reason_t code_; +#if TORRENT_USE_SSL + void operator()(ssl_stream& s) const + { s.next_layer().set_close_reason(code_); } +#endif + void operator()(utp_stream& s) const + { s.set_close_reason(code_); } + template + void operator()(T const&) const {} + }; + + void set_close_reason(socket_type& s, close_reason_t code) + { + boost::apply_visitor(set_close_reason_visitor{code}, s); + } + + struct get_close_reason_visitor { +#if TORRENT_USE_SSL + close_reason_t operator()(ssl_stream& s) const + { return s.next_layer().get_close_reason(); } +#endif + close_reason_t operator()(utp_stream& s) const + { return s.get_close_reason(); } + template + close_reason_t operator()(T const&) const { return close_reason_t::none; } + }; + + close_reason_t get_close_reason(socket_type const& s) + { + return boost::apply_visitor(get_close_reason_visitor{}, s); + } + +#if TORRENT_USE_SSL + struct set_ssl_hostname_visitor + { + set_ssl_hostname_visitor(char const* h, error_code& ec) : hostname_(h), ec_(&ec) {} + template + void operator()(ssl_stream& s) + { + s.set_verify_callback(ssl::host_name_verification(hostname_), *ec_); + ssl_ = s.handle(); + ctx_ = s.context_handle(); + } + template + void operator()(T&) {} + + char const* hostname_; + error_code* ec_; + ssl::stream_handle_type ssl_ = nullptr; + ssl::context_handle_type ctx_ = nullptr; + }; +#endif + + void setup_ssl_hostname(socket_type& s, std::string const& hostname, error_code& ec) + { +#if TORRENT_USE_SSL + // for SSL connections, make sure to authenticate the hostname + // of the certificate + + set_ssl_hostname_visitor visitor{hostname.c_str(), ec}; + boost::apply_visitor(visitor, s); + + if (visitor.ctx_) + ssl::set_server_name_callback(visitor.ctx_, nullptr, nullptr, ec); + + if (visitor.ssl_) + ssl::set_host_name(visitor.ssl_, hostname, ec); +#else + TORRENT_UNUSED(ec); + TORRENT_UNUSED(hostname); + TORRENT_UNUSED(s); +#endif + } + +#if TORRENT_USE_SSL + + struct socket_closer + { + socket_closer(io_context& ioc + , std::shared_ptr holder + , socket_type* s) + : h(std::move(holder)) + , t(std::make_shared(ioc)) + , sock(s) + { + t->expires_after(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; + }; + + struct issue_async_shutdown_visitor + { + issue_async_shutdown_visitor(socket_type* s, std::shared_ptr h) + : holder_(std::move(h)), sock_type_(s) {} + + template + void operator()(ssl_stream& s) + { + // we do this twice, because the socket_closer callback will be + // called twice + ADD_OUTSTANDING_ASYNC("on_close_socket"); + ADD_OUTSTANDING_ASYNC("on_close_socket"); + s.async_shutdown(socket_closer(static_cast(s.get_executor().context()) + , std::move(holder_), sock_type_)); + } + template + void operator()(T& s) + { + error_code e; + s.close(e); + } + std::shared_ptr holder_; + socket_type* sock_type_; + }; +#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) + { +#if TORRENT_USE_SSL + boost::apply_visitor(issue_async_shutdown_visitor{&s, std::move(holder)}, s); +#else + TORRENT_UNUSED(holder); + error_code e; + s.close(e); +#endif // TORRENT_USE_SSL + } +} +} diff --git a/src/socks5_stream.cpp b/src/socks5_stream.cpp new file mode 100644 index 0000000..1460399 --- /dev/null +++ b/src/socks5_stream.cpp @@ -0,0 +1,81 @@ +/* + +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2007, 2009, 2014, 2016-2020, Arvid Norberg +Copyright (c) 2019, 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/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; + } +} diff --git a/src/ssl.cpp b/src/ssl.cpp new file mode 100644 index 0000000..4897017 --- /dev/null +++ b/src/ssl.cpp @@ -0,0 +1,212 @@ +/* + +Copyright (c) 2020, Paul-Louis Ageneau +Copyright (c) 2018, Alexandre Janniaux +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/ssl.hpp" + +#if TORRENT_USE_SSL + +#ifdef TORRENT_USE_OPENSSL +#include // for GENERAL_NAME +#endif + +#ifdef TORRENT_USE_GNUTLS +#include +#endif + +namespace libtorrent { +namespace ssl { + +void set_trust_certificate(native_context_type nc, string_view pem, error_code &ec) +{ +#if defined TORRENT_USE_OPENSSL + // create a new X.509 certificate store + X509_STORE* cert_store = X509_STORE_new(); + if (!cert_store) + { + ec = error_code(int(ERR_get_error()), error::get_ssl_category()); + return; + } + + // wrap the PEM certificate in a BIO, for openssl to read + BIO* bp = BIO_new_mem_buf( + const_cast(pem.data()), + int(pem.size())); + + // parse the certificate into OpenSSL's internal representation + X509* cert = PEM_read_bio_X509_AUX(bp, nullptr, nullptr, nullptr); + BIO_free(bp); + + if (!cert) + { + X509_STORE_free(cert_store); + ec = error_code(int(ERR_get_error()), error::get_ssl_category()); + return; + } + + // add cert to cert_store + X509_STORE_add_cert(cert_store, cert); + X509_free(cert); + + // and lastly, replace the default cert store with ours + SSL_CTX_set_cert_store(nc, cert_store); + +#elif defined TORRENT_USE_GNUTLS + gnutls_datum_t ca; + ca.data = reinterpret_cast(const_cast(pem.data())); + ca.size = unsigned(pem.size()); + + // Warning: returns the number of certificates processed or a negative error code on error + int ret = gnutls_certificate_set_x509_trust_mem(nc, &ca, GNUTLS_X509_FMT_PEM); + if(ret < 0) + ec = error_code(ret, error::get_ssl_category()); +#endif +} + +void set_server_name_callback(context_handle_type c, server_name_callback_type cb, void* arg, error_code& ec) +{ +#if defined TORRENT_USE_OPENSSL + TORRENT_UNUSED(ec); +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + SSL_CTX_set_tlsext_servername_callback(c, cb); + SSL_CTX_set_tlsext_servername_arg(c, arg); +#if defined __clang__ +#pragma clang diagnostic pop +#endif + +#elif defined TORRENT_USE_GNUTLS + if(cb) + c->set_server_name_callback( + [cb, arg](stream_base& s, std::string const& name) + { + return cb(&s, name, arg); + } + , ec); + else + c->set_server_name_callback(nullptr); +#endif +} + +void set_host_name(stream_handle_type s, std::string const& name, error_code& ec) +{ +#if defined TORRENT_USE_OPENSSL + TORRENT_UNUSED(ec); +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wcast-qual" +#endif + SSL_set_tlsext_host_name(s, name.c_str()); +#if defined __clang__ +#pragma clang diagnostic pop +#endif + +#elif defined TORRENT_USE_GNUTLS + s->set_host_name(name, ec); +#endif +} + +void set_context(stream_handle_type s, context_handle_type c) +{ +#if defined TORRENT_USE_OPENSSL + SSL_set_SSL_CTX(s, c); + SSL_set_verify(s + , SSL_CTX_get_verify_mode(c) + , SSL_CTX_get_verify_callback(c)); +#elif defined TORRENT_USE_GNUTLS + s->set_context(*c); +#endif +} + +bool has_context(stream_handle_type s, context_handle_type c) +{ + context_handle_type stream_ctx = get_context(s); +#if defined TORRENT_USE_OPENSSL + return stream_ctx == c; +#elif defined TORRENT_USE_GNUTLS + return stream_ctx->native_handle() == c->native_handle(); +#endif +} + +context_handle_type get_context(stream_handle_type s) +{ +#if defined TORRENT_USE_OPENSSL + return SSL_get_SSL_CTX(s); +#elif defined TORRENT_USE_GNUTLS + return &s->get_context(); +#endif +} + +#if defined TORRENT_USE_OPENSSL +namespace { + struct lifecycle + { + lifecycle() + { + // 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 + } + + ~lifecycle() + { +// 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 +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + // openssl requires this to clean up internal structures it allocates + CRYPTO_cleanup_all_ex_data(); +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic pop +#endif +#endif + } + } global; +} +#endif + +} // ssl +} // libtorrent +#endif + diff --git a/src/stack_allocator.cpp b/src/stack_allocator.cpp new file mode 100644 index 0000000..affff54 --- /dev/null +++ b/src/stack_allocator.cpp @@ -0,0 +1,143 @@ +/* + +Copyright (c) 2017-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 "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..36c2b10 --- /dev/null +++ b/src/stat.cpp @@ -0,0 +1,46 @@ +/* + +Copyright (c) 2003, 2008-2010, 2019, Arvid Norberg +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/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..2fc9784 --- /dev/null +++ b/src/stat_cache.cpp @@ -0,0 +1,145 @@ +/* + +Copyright (c) 2010, 2013-2019, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016-2017, 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/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 -1; + } + + 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_utils.cpp b/src/storage_utils.cpp new file mode 100644 index 0000000..435cf41 --- /dev/null +++ b/src/storage_utils.cpp @@ -0,0 +1,807 @@ +/* + +Copyright (c) 2016-2020, Arvid Norberg +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2017-2018, Alden Torres +Copyright (c) 2018, Pavel Pimenov +Copyright (c) 2019, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/session.hpp" // for session::delete_files +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_view.hpp" + +#if TORRENT_HAS_SYMLINK +#include // for symlink() +#endif + +#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); + TORRENT_ASSERT(bytes_transferred <= file_bytes_left); + 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.operation = operation_t::file_read; + ec.ec = boost::asio::error::eof; + ec.file(file_index); + } + return size - bytes_left; + } + } + return size; + } + + std::pair move_storage(file_storage const& f + , std::string save_path + , std::string const& destination_save_path + , std::function const& move_partfile + , 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)); + + error_code ignore; + if (flags == move_flags_t::dont_replace && exists(new_path, ignore)) + { + 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 && move_partfile) + { + 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 (!path_equal(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; + } + } + } + + // when we're deleting "files", we also delete the part file + if ((options & session::delete_partfile) + || (options & session::delete_files)) + { + 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 + bool added_files = false; + 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 (err == boost::system::errc::no_such_file_or_directory) + { + // we create directories lazily, so it's possible it hasn't + // been created yet. Create the directories now and try + // again + create_directories(parent_path(file_path), err); + + if (err) + { + ec.file(idx); + ec.operation = operation_t::mkdir; + return false; + } + + hard_link(s, file_path, err); + } + + // if the file already exists, that's not an error + if (err == boost::system::errc::file_exists) + continue; + + // 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) + { + ec.ec = err; + ec.file(idx); + ec.operation = operation_t::file_hard_link; + return false; + } + added_files = true; + stat.set_dirty(idx); + } + } +#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; + } + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + // always trigger a full recheck when we pull in files from other + // torrents, via hard links + // TODO: it would seem reasonable to, instead, set the have_pieces bits + // for the pieces representing these files, and resume with the normal + // logic + if (added_files) return false; +#endif + + // parse have bitmask. Verify that the files we expect to have + // actually do exist + piece_index_t const end_piece = std::min(rd.have_pieces.end_index(), fs.end_piece()); + for (piece_index_t i(0); i < end_piece; ++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; + } + + int read_zeroes(span bufs) + { + int ret = 0; + for (auto buf : bufs) + { + ret += static_cast(buf.size()); + std::fill(buf.begin(), buf.end(), '\0'); + } + return ret; + } + + void initialize_storage(file_storage const& fs + , std::string const& save_path + , stat_cache& sc + , aux::vector const& file_priority + , std::function create_file + , std::function create_link + , std::function oversized_file + , storage_error& ec) + { + // create zero-sized files + for (auto const file_index : fs.file_range()) + { + // ignore files that have priority 0 + if (file_priority.end_index() > file_index + && 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; + auto const sz = sc.get_filesize(file_index, fs, 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; + } + + auto const fs_file_size = fs.file_size(file_index); + if (!err && sz > fs_file_size) + { + // this file is oversized, alert the client + oversized_file(file_index, sz); + } + + // 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 == 0) + { + // create symlinks + if (fs.file_flags(file_index) & file_storage::flag_symlink) + { +#if TORRENT_HAS_SYMLINK + std::string const path = fs.file_path(file_index, save_path); + // 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)); + create_link(target, path, ec); + if (ec.ec) + { + ec.file(file_index); + return; + } +#else + TORRENT_UNUSED(create_link); +#endif + } + else if (err == boost::system::errc::no_such_file_or_directory) + { + // 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 + ec.ec.clear(); + create_file(file_index, ec); + if (ec) return; + } + } + ec.ec.clear(); + } + } + + void create_symlink(std::string const& target, std::string const& link, storage_error& ec) + { +#if TORRENT_HAS_SYMLINK + create_directories(parent_path(link), ec.ec); + if (ec) + { + ec.ec = error_code(errno, generic_category()); + ec.operation = operation_t::mkdir; + return; + } + 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.operation = operation_t::symlink; + return; + } + } + else + { + ec.ec = error_code(error, generic_category()); + ec.operation = operation_t::symlink; + return; + } + } +#else + TORRENT_UNUSED(target); + TORRENT_UNUSED(link); + TORRENT_UNUSED(ec); +#endif + } + +}} diff --git a/src/string_util.cpp b/src/string_util.cpp new file mode 100644 index 0000000..0414a78 --- /dev/null +++ b/src/string_util.cpp @@ -0,0 +1,412 @@ +/* + +Copyright (c) 2012, 2014-2020, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_quotes(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) + { + auto const pos = last.find(sep); + if (pos == string_view::npos) return {last, {}}; + else return {last.substr(0, pos), last.substr(pos + 1)}; + } + + std::pair split_string_quotes(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)}; + } + + void ltrim(std::string& s) + { + while (!s.empty() && is_space(s.front())) + s.erase(s.begin()); + } + +#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..156300e --- /dev/null +++ b/src/time.cpp @@ -0,0 +1,40 @@ +/* + +Copyright (c) 2009, 2015, 2017-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 "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..830d632 --- /dev/null +++ b/src/timestamp_history.cpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2010, 2014, 2016, 2018-2019, Arvid Norberg +Copyright (c) 2016-2017, 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/aux_/timestamp_history.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +namespace libtorrent { +namespace aux { + +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..500f572 --- /dev/null +++ b/src/torrent.cpp @@ -0,0 +1,11995 @@ +/* + +Copyright (c) 2003-2020, Arvid Norberg +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2008, Andrew Resch +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2015-2020, Steven Siloti +Copyright (c) 2016, Jonathan McDougall +Copyright (c) 2016-2020, Alden Torres +Copyright (c) 2016-2018, Pavel Pimenov +Copyright (c) 2016-2017, Andrei Kurushin +Copyright (c) 2017, Falcosc +Copyright (c) 2017, 2020, AllSeeingEyeTolledEweSew +Copyright (c) 2017, ximply +Copyright (c) 2018, Fernando Rodriguez +Copyright (c) 2018, d-komarov +Copyright (c) 2018, airium +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 + +#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/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/aux_/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/aux_/alert_manager.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" // for is_ip_address +#include "libtorrent/download_priority.hpp" +#include "libtorrent/hex.hpp" // to_hex +#include "libtorrent/aux_/range.hpp" +#include "libtorrent/aux_/merkle.hpp" +#include "libtorrent/mmap_disk_io.hpp" // for hasher_thread_divisor +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/aux_/generate_peer_id.hpp" +#include "libtorrent/aux_/announce_entry.hpp" +#include "libtorrent/ssl.hpp" +#include "libtorrent/aux_/apply_pad_files.hpp" + +#ifdef TORRENT_SSL_PEERS +#include "libtorrent/ssl_stream.hpp" +#endif // TORRENT_SSL_PEERS + +#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::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; + constexpr web_seed_flag_t torrent::no_local_ips; + + 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&& 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_context()) + , m_inactivity_timer(ses.get_context()) + , m_trackerid(p.trackerid) + , m_save_path(complete(p.save_path)) + , m_stats_counters(ses.stats_counters()) + , m_added_time(p.added_time ? p.added_time : std::time(nullptr)) + , m_completed_time(p.completed_time) + , m_last_seen_complete(p.last_seen_complete) + , m_swarm_last_seen_complete(p.last_seen_complete) + , m_info_hash(p.info_hashes) + , 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_num_uploads(0) + , m_enable_pex(!bool(p.flags & torrent_flags::disable_pex)) + , m_apply_ip_filter(p.flags & torrent_flags::apply_ip_filter) + , m_pending_active_change(false) + , m_v2_piece_layers_validated(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_userdata(p.userdata) + , 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. + +#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 (!m_torrent_file) + m_torrent_file = (p.ti ? p.ti : std::make_shared(m_info_hash)); + + if (m_torrent_file->is_valid()) + { + error_code ec = initialize_merkle_trees(); + if (ec) throw system_error(ec); + m_size_on_disk = aux::size_on_disk(m_torrent_file->files()); + } + + // --- 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)) + { + m_trackers.clear(); + for (auto const& ae : m_torrent_file->trackers()) + m_trackers.emplace_back(ae); + } + + int tier = 0; + auto tier_iter = p.tracker_tiers.begin(); + for (auto const& url : p.trackers) + { + aux::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() + , [] (aux::announce_entry const& lhs, aux::announce_entry const& rhs) + { return lhs.tier < rhs.tier; }); + + if (settings().get_bool(settings_pack::prefer_udp_trackers)) + prioritize_udp_trackers(); + + 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)); + } + + 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; + + // --- V2 HASHES --- + + if (m_torrent_file->is_valid() && m_torrent_file->info_hashes().has_v2()) + { + if (!p.merkle_trees.empty()) + load_merkle_trees( + std::move(p.merkle_trees) + , std::move(p.merkle_tree_mask) + , std::move(p.verified_leaf_hashes)); + + // we really don't want to store extra copies of the trees + TORRENT_ASSERT(p.merkle_trees.empty()); + } + + if (valid_metadata()) + { + inc_stats_counter(counters::num_total_pieces_added + , m_torrent_file->num_pieces()); + } + + // TODO: 3 we could probably get away with just saving a few fields here + m_add_torrent_params = std::make_unique(std::move(p)); + } + + void torrent::load_merkle_trees( + aux::vector, file_index_t> trees_import + , aux::vector, file_index_t> mask + , aux::vector, file_index_t> verified) + { + auto const& fs = m_torrent_file->orig_files(); + + std::vector const empty_verified; + for (file_index_t i{0}; i < fs.end_file(); ++i) + { + if (fs.pad_file_at(i) || fs.file_size(i) == 0) + continue; + + if (i >= trees_import.end_index()) break; + std::vector const& verified_bitmask = (i >= verified.end_index()) ? empty_verified : verified[i]; + if (i < mask.end_index() && !mask[i].empty()) + { + mask[i].resize(m_merkle_trees[i].size(), false); + m_merkle_trees[i].load_sparse_tree(trees_import[i], mask[i], verified_bitmask); + } + else + { + m_merkle_trees[i].load_tree(trees_import[i], verified_bitmask); + } + } + } + + void torrent::inc_stats_counter(int c, int value) + { m_ses.stats_counters().inc_stats_counter(c, value); } + + 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) + { + str += peer.address().to_string(); + 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 (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 + } + + 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); + } + + set_need_save_resume(); + + 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; + + // 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() + , [](aux::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; + + disk_job_flags_t flags{}; + auto const read_mode = settings().get_int(settings_pack::disk_io_read_mode); + if (read_mode == settings_pack::disable_os_cache) + flags |= disk_interface::volatile_read; + + peer_request r; + r.piece = piece; + r.start = 0; + auto self = shared_from_this(); + 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 + , [self, r, rp](disk_buffer_holder block, storage_error const& se) mutable + { self->on_disk_read_complete(std::move(block), se, r, rp); } + , flags); + } + m_ses.deferred_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) + { + bool const new_value = !bool(flags & torrent_flags::disable_dht); + if (m_enable_dht != new_value) set_need_save_resume(); + m_enable_dht = new_value; + } + if (mask & torrent_flags::disable_lsd) + { + bool const new_value = !bool(flags & torrent_flags::disable_lsd); + if (m_enable_lsd != new_value) set_need_save_resume(); + m_enable_lsd = new_value; + } + if (mask & torrent_flags::disable_pex) + { + bool const new_value = !bool(flags & torrent_flags::disable_pex); + if (m_enable_pex != new_value) set_need_save_resume(); + m_enable_pex = new_value; + } + } + +#ifndef TORRENT_DISABLE_SHARE_MODE + void torrent::set_share_mode(bool s) + { + if (s == m_share_mode) return; + + m_share_mode = s; + set_need_save_resume(); +#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 + + set_need_save_resume(); + 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 = std::make_unique(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 const piece, piece_block) try + { + if (m_abort) return; + + // the user may have called force_recheck, which clears + // the piece picker + if (has_picker()) + { + // unlock the piece and restore it, as if no block was + // ever downloaded for it. + m_picker->restore_piece(piece); + } + + 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 + , 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.data(), 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(); + } + + 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); + + auto pp = std::make_unique(m_torrent_file->total_size() + , m_torrent_file->piece_length()); + + 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); + } + } + + void torrent::need_hash_picker() + { + if (m_hash_picker) return; + + TORRENT_ASSERT(valid_metadata()); + TORRENT_ASSERT(m_connections_initialized); + + //INVARIANT_CHECK; + + m_hash_picker.reset(new hash_picker(m_torrent_file->orig_files() + , m_merkle_trees)); + } + + 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}; + auto self = shared_from_this(); + for (int i = 0; i < blocks_in_piece; ++i, p.start += block_size()) + { + piece_block const block(piece, i); + + bool const finished = picker().is_finished(block); + + // if this block is already finished, only resume if we have the + // flag set to overwrite existing data. + if (!(flags & torrent_handle::overwrite_existing) && finished) + continue; + + bool const downloaded = picker().is_downloaded(block); + + // however, if the block is downloaded by not written to disk yet, + // we can't (easily) replace it. We would have to synchronize with + // the disk in a clear_piece() call. Instead, just ignore such + // blocks. + if (downloaded && !finished) + continue; + + p.length = std::min(piece_size - p.start, block_size()); + + m_stats_counters.inc_stats_counter(counters::queued_write_bytes, p.length); + + disk_job_flags_t dflags{}; + + auto const write_mode = settings().get_int(settings_pack::disk_io_write_mode); + if (write_mode == settings_pack::disable_os_cache) + dflags |= disk_interface::flush_piece | disk_interface::volatile_read; + + m_ses.disk_thread().async_write(m_storage, p, data + p.start, nullptr + , [self, p](storage_error const& error) { self->on_disk_write_complete(error, p); } + , dflags); + + 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); + } + } + m_ses.deferred_submit_jobs(); + } + + 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(); } + + 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&, client_data_t)> const& ext + , client_data_t 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_SSL_PEERS + bool torrent::verify_peer_cert(bool const preverified, ssl::verify_context& ctx) + { + // if the cert wasn't signed by the correct CA, fail the verification + if (!preverified) return false; + + std::string expected = m_torrent_file->name(); +#ifndef TORRENT_DISABLE_LOGGING + std::string names; + bool match = false; +#endif + +#ifdef TORRENT_USE_OPENSSL +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#pragma clang diagnostic ignored "-Wused-but-marked-unused" +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif + // we're only interested in checking the certificate at the end of the chain. + // 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)); + + for (int i = 0; i < sk_GENERAL_NAME_num(gens); ++i) + { + GENERAL_NAME* gen = sk_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); + auto 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, expected.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); + auto 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, expected.c_str(), name_length) == 0) + { +#ifdef TORRENT_DISABLE_LOGGING + return true; +#else + match = true; +#endif + + } + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#elif defined TORRENT_USE_GNUTLS + gnutls_x509_crt_t cert = ctx.native_handle(); + + // We don't use gnutls_x509_crt_check_hostname() + // as it doesn't handle wildcards the way we need here + + char buf[256]; + unsigned int seq = 0; + while(true) { + size_t len = sizeof(buf); + int ret = gnutls_x509_crt_get_subject_alt_name(cert, seq, buf, &len, nullptr); + if(ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) break; + if(ret == GNUTLS_E_SUCCESS) + { +#ifndef TORRENT_DISABLE_LOGGING + if (!names.empty()) names += " | n: "; + names.append(buf, len); +#endif + if (std::strncmp(buf, "*", len) == 0 + || std::strncmp(buf, expected.c_str(), len) == 0) + { +#ifndef TORRENT_DISABLE_LOGGING + match = true; + continue; +#else + return true; +#endif + } + } + ++seq; + } + + // no match in the alternate names, so try the common name + size_t len = sizeof(buf); + int ret = gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, buf, &len); + if(ret == GNUTLS_E_SUCCESS) + { +#ifndef TORRENT_DISABLE_LOGGING + if (!names.empty()) names += " | n: "; + names.append(buf, len); +#endif + if (std::strncmp(buf, "*", len) == 0 + || std::strncmp(buf, expected.c_str(), len) == 0) + { +#ifdef TORRENT_DISABLE_LOGGING + return true; +#else + match = true; +#endif + } + } +#endif // TORRENT_USE_GNUTLS + +#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) + { + // create the SSL context for this torrent. We need to + // inject the root certificate, and no other, to + // verify other peers against + std::unique_ptr ctx(std::make_unique(ssl::context::tls)); + + ctx->set_options(ssl::context::default_workarounds + | ssl::context::no_sslv2 + | ssl::context::no_sslv3 + | ssl::context::single_dh_use); + + error_code ec; + ctx->set_verify_mode(ssl::context::verify_peer + | ssl::context::verify_fail_if_no_peer_cert + | ssl::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; + } + + // set the root certificate as trust + ssl::set_trust_certificate(ctx->native_handle(), cert, ec); + if (ec) + { + set_error(ec, torrent_status::error_file_ssl_ctx); + pause(); + return; + } + +#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()); + } +#endif // TORRENT_SSL_PEERS + + 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.get_best() + }; + + // the shared_from_this() will create an intentional + // cycle of ownership, se the hpp file for description. + m_storage = m_ses.disk_thread().new_torrent(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_SSL_PEERS + 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()) + { + // m_file_priority was loaded from the resume data, this doesn't + // alter any state that needs to be saved in the resume data + bool const ns = m_need_save_resume_data; + update_piece_priorities(m_file_priority); + m_need_save_resume_data = ns; + } + + 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); + + m_padding_bytes = 0; + std::vector have_pieces; + + aux::apply_pad_files(fs, [&](piece_index_t const piece, int const bytes) + { + m_padding_bytes += bytes; + if (bytes == fs.piece_size(piece)) + have_pieces.push_back(piece); + m_picker->set_pad_bytes(piece, bytes); + }); + + for (auto i : have_pieces) + { + // we may have this piece already, if we picked it up from + // resume data. + if (m_picker->have_piece(i)) continue; +// 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(info_hash_t(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_file(), 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_file(), t->save_path()); + } + } + + std::vector const& l = res.get_links(); + if (!l.empty()) + { + links.resize(m_torrent_file->files().num_files()); + for (auto const& i : l) + { + if (!i.ti) continue; + links[i.file_idx] = 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 + + if (!m_add_torrent_params || !(m_add_torrent_params->flags & torrent_flags::no_verify_files)) + { + m_ses.disk_thread().async_check_files( + m_storage, m_add_torrent_params ? m_add_torrent_params.get() : nullptr + , std::move(links), [self = shared_from_this()](status_t st, storage_error const& error) + { self->on_resume_data_checked(st, error); }); +#ifndef TORRENT_DISABLE_LOGGING + debug_log("init, async_check_files"); +#endif + m_ses.deferred_submit_jobs(); + } + else + { + on_resume_data_checked(status_t::no_error, storage_error{}); + } + + 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 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::oversized_file) != status_t{}) + { + // clear the flag + status = status & ~status_t::oversized_file; + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle()); + } + + 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()) + { + std::string str; + for (auto const& peer : m_add_torrent_params->peers) + { + str += peer.address().to_string(); + 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); + + // 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) + { + m_picker->resize(m_torrent_file->total_size(), m_torrent_file->piece_length()); + + 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); + + m_ses.disk_thread().async_check_files(m_storage, nullptr + , {}, [self = shared_from_this()](status_t st, storage_error const& error) + { self->on_force_recheck(st, error); }); + m_ses.deferred_submit_jobs(); + } + + void torrent::on_force_recheck(status_t 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 ((status & status_t::oversized_file) != status_t{}) + { + // clear the flag + status = status & ~status_t::oversized_file; + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle()); + } + + 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() try + { + 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 + = std::max(1, settings().get_int(settings_pack::hashing_threads)) * 2; + if (num_outstanding < min_outstanding) num_outstanding = min_outstanding; + + // 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) return; + + // 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; + } + + for (int i = 0; i < num_outstanding; ++i) + { + if (has_picker()) + { + // skip pieces we already have + while (m_checking_piece < m_torrent_file->end_piece() + && m_picker->have_piece(m_checking_piece)) + { + ++m_checking_piece; + ++m_num_checked_pieces; + } + } + + if (m_checking_piece >= m_torrent_file->end_piece()) break; + + auto flags = disk_interface::sequential_access | disk_interface::volatile_read; + if (torrent_file().info_hashes().has_v1()) + flags |= disk_interface::v1_hash; + aux::vector hashes; + if (torrent_file().info_hashes().has_v2()) + hashes.resize(torrent_file().orig_files().blocks_in_piece2(m_checking_piece)); + + span v2_span(hashes); + m_ses.disk_thread().async_hash(m_storage, m_checking_piece, v2_span, flags + , [self = shared_from_this(), hashes = std::move(hashes)] + (piece_index_t p, sha1_hash const& h, storage_error const& error) mutable + { self->on_piece_hashed(std::move(hashes), p, h, error); }); + ++m_checking_piece; + if (m_checking_piece >= m_torrent_file->end_piece()) break; + } + m_ses.deferred_submit_jobs(); +#ifndef TORRENT_DISABLE_LOGGING + debug_log("start_checking, m_checking_piece: %d" + , static_cast(m_checking_piece)); +#endif + } + catch (...) { handle_exception(); } + + // This is only used for checking of torrents. i.e. force-recheck or initial checking + // of existing files + void torrent::on_piece_hashed(aux::vector block_hashes + , 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 + || error.ec == lt::errors::file_too_short +#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()); + + boost::tribool hash_passed[2] + = { boost::indeterminate, boost::indeterminate }; + + if (!settings().get_bool(settings_pack::disable_hash_checks)) + { + if (torrent_file().info_hashes().has_v1()) + hash_passed[0] = piece_hash == m_torrent_file->hash_for_piece(piece); + + // if the v1 hash failed the check, don't add the v2 hashes to the + // merkle tree. They are most likely invalid. + if (torrent_file().info_hashes().has_v2() && !bool(hash_passed[0] == false)) + { + hash_passed[1] = on_blocks_hashed(piece, block_hashes); + } + } + else + { + hash_passed[0] = hash_passed[1] = true; + } + + if ((hash_passed[0] && !hash_passed[1]) || (!hash_passed[0] && hash_passed[1])) + { + set_error(errors::torrent_inconsistent_hashes, torrent_status::error_file_none); + pause(); + return; + } + else if (hash_passed[0] || hash_passed[1]) + { + if (has_picker() || !m_have_all) + { + need_picker(); + m_picker->we_have(piece); + update_gauge(); + } + we_have(piece); + } + else if (!error + && boost::indeterminate(hash_passed[0]) + && boost::indeterminate(hash_passed[1])) + { + // The data exists but we don't have the hashes needed to verify + // it yet. This is a special case because we want to say we have + // the piece once the hash is verified and not download the data + // unless the hash check fails. To get this effect we setup the + // piece's state in the piece picker so that it looks like a piece + // which is finished but not hash checked. + need_picker(); + int const blocks_in_piece = m_picker->blocks_in_piece(piece); + for (int i = 0; i < blocks_in_piece; ++i) + m_picker->mark_as_finished(piece_block(piece, i), nullptr); + } + + if (m_checking_piece < m_torrent_file->end_piece()) + { + // skip pieces we already have + while (m_picker->have_piece(m_checking_piece)) + { + ++m_checking_piece; + ++m_num_checked_pieces; + 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 + break; + } + } + } + + if (m_num_checked_pieces < m_torrent_file->end_piece()) + { + // 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; + } + + if (m_checking_piece >= m_torrent_file->end_piece()) + return; + + auto flags = disk_interface::sequential_access | disk_interface::volatile_read; + + if (torrent_file().info_hashes().has_v1()) + flags |= disk_interface::v1_hash; + if (torrent_file().info_hashes().has_v2()) + block_hashes.resize(torrent_file().orig_files().blocks_in_piece2(m_checking_piece)); + + span v2_span(block_hashes); + m_ses.disk_thread().async_hash(m_storage, m_checking_piece, v2_span, flags + , [self = shared_from_this(), hashes = std::move(block_hashes)] + (piece_index_t p, sha1_hash const& h, storage_error const& e) + { self->on_piece_hashed(std::move(hashes), p, h, e); }); + ++m_checking_piece; + m_ses.deferred_submit_jobs(); +#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_SSL_PEERS + 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_torrent_file->info_hashes().for_each([&](sha1_hash const& ih, protocol_version) + { + m_ses.announce_lsd(ih, 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 (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() + , [](aux::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_torrent_file->info_hashes().for_each([&](sha1_hash const& ih, protocol_version v) + { + m_ses.dht()->announce(ih, 0, flags + , std::bind(&torrent::on_dht_announce_response_disp, self, v, _1)); + }); + } + + void torrent::on_dht_announce_response_disp(std::weak_ptr const t + , protocol_version const v, std::vector const& peers) + { + std::shared_ptr tor = t.lock(); + if (!tor) return; + tor->on_dht_announce_response(v, peers); + } + + void torrent::on_dht_announce_response(protocol_version const v + , 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, v == protocol_version::V2 ? pex_lt_v2 : pex_flags_t(0)); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log() && !peers.empty()) + { + std::string str; + for (auto const& peer : peers) + { + str += peer.address().to_string(); + 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 { + void refresh_endpoint_list(aux::session_interface& ses + , bool const is_ssl, bool const complete_sent + , std::vector& aeps) + { + // update the endpoint list by adding entries for new listen sockets + // and removing entries for non-existent ones + std::size_t valid_endpoints = 0; + ses.for_each_listen_socket([&](aux::listen_socket_handle const& s) { + if (s.is_ssl() != is_ssl) + return; + for (auto& aep : aeps) + { + if (aep.socket != s) continue; + std::swap(aeps[valid_endpoints], aep); + valid_endpoints++; + return; + } + + aeps.emplace_back(s, complete_sent); + std::swap(aeps[valid_endpoints], aeps.back()); + valid_endpoints++; + }); + + TORRENT_ASSERT(valid_endpoints <= aeps.size()); + aeps.erase(aeps.begin() + int(valid_endpoints), aeps.end()); + } +} + + namespace + { + struct announce_protocol_state + { + // 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; + }; + + struct announce_state + { + explicit announce_state(aux::listen_socket_handle s) + : socket(std::move(s)) {} + + aux::listen_socket_handle socket; + + aux::array state; + }; + } + + void torrent::announce_with_tracker(event_t e) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(e == event_t::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 = event_t::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 == event_t::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 != event_t::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 != event_t::stopped && m_paused) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** announce: event != stopped && m_paused"); +#endif + return; + } + + TORRENT_ASSERT(!m_paused || e == event_t::stopped); + + if (e == event_t::none && is_finished() && !is_seed()) + e = event_t::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.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_SSL_PEERS + // 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 (ep.address().is_unspecified()) return; + if (aux::is_v6(ep)) + { + if (!aux::is_local(ep.address()) && !ep.address().is_loopback()) + req.ipv6.push_back(ep.address().to_v6()); + } + else + { + if (!aux::is_local(ep.address()) && !ep.address().is_loopback()) + req.ipv4.push_back(ep.address().to_v4()); + } + }); + } + + // if we are aborting. we don't want any new peers + req.num_want = (req.event == event_t::stopped) + ? 0 : settings().get_int(settings_pack::num_want); + +// some older versions of clang had a bug where it would fire this warning here +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-braces" +#endif + aux::array const supports_protocol + { { + m_info_hash.has_v1(), + m_info_hash.has_v2() + } }; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + time_point32 const now = aux::time_now32(); + + // each listen socket gets its 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 + refresh_endpoint_list(m_ses, is_ssl_torrent(), bool(m_complete_sent), ae.endpoints); + + // 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& ep_state = *aep_state_iter; + + if (!aep.enabled) continue; + + for (protocol_version const ih : all_versions) + { + if (!supports_protocol[ih]) continue; + + auto& state = ep_state.state[ih]; + auto& a = aep.info_hashes[ih]; + + // if we haven't sent an event=start to the tracker, there's no + // point in sending an event=stopped + if (!a.start_sent && req.event == event_t::stopped) + continue; + + if (state.done) 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, a.is_working(), ae.fail_limit + , a.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 (a.is_working()) { state.tier = ae.tier; state.sent_announce = false; } + if (!a.can_announce(now, is_seed(), ae.fail_limit)) + { + // this counts + if (a.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 == event_t::none) + { + if (!a.start_sent) req.event = event_t::started; + else if (!m_complete_sent + && !a.complete_sent + && is_seed()) + { + req.event = event_t::completed; + } + } + + req.triggered_manually = a.triggered_manually; + a.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; + req.info_hash = m_torrent_file->info_hashes().get(protocol_version(ih)); + +#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 == event_t::stopped ? "stopped" + : req.event == event_t::started ? "started" : "") + , m_abort +#ifdef TORRENT_SSL_PEERS + , static_cast(req.ssl_ctx) +#else + , static_cast(nullptr) +#endif + , m_ses.listen_port() + , m_ses.ssl_listen_port() + , a.fails + , a.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(req, tl); + } + else +#endif + { + m_ses.queue_tracker_request(req, shared_from_this()); + } + + a.updating = true; + a.next_announce = now; + a.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 (a.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() + , [supports_protocol](announce_state const& s) { + for (protocol_version const ih : all_versions) + { + if (supports_protocol[ih] && !s.state[ih].done) + return false; + } + return true; + })) + 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.kind |= tracker_request::scrape_request; + auto& ae = m_trackers[idx]; + refresh_endpoint_list(m_ses, is_ssl_torrent(), bool(m_complete_sent), ae.endpoints); + req.url = ae.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; + for (aux::announce_endpoint const& aep : ae.endpoints) + { + if (!aep.enabled) continue; + req.outgoing_socket = aep.socket; + m_torrent_file->info_hashes().for_each([&](sha1_hash const& ih, protocol_version) + { + req.info_hash = ih; + m_ses.queue_tracker_request(req, shared_from_this()); + }); + } + } + + void torrent::tracker_warning(tracker_request const& req, std::string const& msg) + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + + protocol_version const hash_version = req.info_hash == m_info_hash.v1 + ? protocol_version::V1 : protocol_version::V2; + + aux::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.info_hashes[hash_version].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(req.kind & tracker_request::scrape_request); + + protocol_version const hash_version = req.info_hash == m_info_hash.v1 + ? protocol_version::V1 : protocol_version::V2; + + aux::announce_entry* ae = find_tracker(req.url); + tcp::endpoint local_endpoint; + if (ae) + { + auto* aep = ae->find_endpoint(req.outgoing_socket); + if (aep) + { + local_endpoint = aep->local_endpoint; + if (incomplete >= 0) aep->info_hashes[hash_version].scrape_incomplete = incomplete; + if (complete >= 0) aep->info_hashes[hash_version].scrape_complete = complete; + if (downloaded >= 0) aep->info_hashes[hash_version].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) + { + for (protocol_version const ih : all_versions) + { + auto& a = aep.info_hashes[ih]; + complete = std::max(a.scrape_complete, complete); + incomplete = std::max(a.scrape_incomplete, incomplete); + downloaded = std::max(a.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(!(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() && !tracker_ip.is_unspecified() && 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(); + + protocol_version const v = r.info_hash == torrent_file().info_hashes().v1 + ? protocol_version::V1 : protocol_version::V2; + + auto const interval = std::max(resp.interval, seconds32( + settings().get_int(settings_pack::min_announce_interval))); + + aux::announce_entry* ae = find_tracker(r.url); + tcp::endpoint local_endpoint; + if (ae) + { + auto* aep = ae->find_endpoint(r.outgoing_socket); + if (aep) + { + auto& a = aep->info_hashes[v]; + + local_endpoint = aep->local_endpoint; + if (resp.incomplete >= 0) a.scrape_incomplete = resp.incomplete; + if (resp.complete >= 0) a.scrape_complete = resp.complete; + if (resp.downloaded >= 0) a.scrape_downloaded = resp.downloaded; + if (!a.start_sent && r.event == event_t::started) + a.start_sent = true; + if (!a.complete_sent && r.event == event_t::completed) + { + a.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; + a.next_announce = now + interval; + a.min_announce = now + resp.min_interval; + a.updating = false; + a.fails = 0; + a.last_error.clear(); + a.message = !resp.warning_message.empty() ? resp.warning_message : std::string(); + int const 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() + , [self = shared_from_this()] (error_code const& ec, char const* dest) + { self->torrent::on_i2p_resolve(ec, dest); }); + } + 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, aux::resolver_interface::abort_on_shutdown + , std::bind(&torrent::on_peer_name_lookup, shared_from_this(), _1, _2, i.port, v)); + } + } + + // 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 + + pex_flags_t flags = v == protocol_version::V2 ? pex_lt_v2 : pex_flags_t(0); + + 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, flags) != 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, flags) != nullptr); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log() && (!resp.peers4.empty() || !resp.peers6.empty())) + { + std::string str; + for (auto const& peer : resp.peers4) + { + str += address_v4(peer.ip).to_string(); + str += ' '; + } + for (auto const& peer : resp.peers6) + { + str += address_v6(peer.ip).to_string(); + 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; +#ifndef TORRENT_DISABLE_LOGGING + bool found_one = false; +#endif + if (tracker_idx == -1) + { + for (auto& e : m_trackers) + { + // make sure we check for new endpoints from the listen sockets + refresh_endpoint_list(m_ses, is_ssl_torrent(), bool(m_complete_sent), e.endpoints); + for (auto& aep : e.endpoints) + { + for (auto& a : aep.info_hashes) + { + a.next_announce = (flags & torrent_handle::ignore_min_interval) + ? time_point_cast(t) + seconds32(1) + : std::max(time_point_cast(t), a.min_announce) + seconds32(1); + a.min_announce = a.next_announce; + a.triggered_manually = true; + } + } + } + } + else + { + if (tracker_idx < 0 || tracker_idx >= int(m_trackers.size())) + return; + aux::announce_entry& e = m_trackers[tracker_idx]; + for (auto& aep : e.endpoints) + { + for (auto& a : aep.info_hashes) + { + a.next_announce = (flags & torrent_handle::ignore_min_interval) + ? time_point_cast(t) + seconds32(1) + : std::max(time_point_cast(t), a.min_announce) + seconds32(1); + a.min_announce = a.next_announce; + a.triggered_manually = true; +#ifndef TORRENT_DISABLE_LOGGING + found_one = true; +#endif + } + } + } + +#ifndef TORRENT_DISABLE_LOGGING + if (!found_one) + { + debug_log("*** found no tracker endpoints to announce"); + } +#endif + 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 + , protocol_version const v) 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()) + { + debug_log("blocked ip from tracker: %s", host.address().to_string().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, v == protocol_version::V2 ? pex_lt_v2 : pex_flags_t(0))) + { + state_updated(); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("name-lookup add_peer() [ %s ] connect-candidates: %d" + , host.address().to_string().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_bytes > 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)); + + // every block should not be a pad block + TORRENT_ASSERT(pc.pad_bytes <= std::int64_t(pc.num_pieces) * fs.piece_length()); + + 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_bytes); + } + + // 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_size_on_disk; + + TORRENT_ASSERT(st.total_wanted <= m_torrent_file->total_size()); + TORRENT_ASSERT(st.total_wanted >= 0); + + TORRENT_ASSERT(!valid_metadata() || m_torrent_file->num_pieces() > 0); + if (!valid_metadata()) return; + + if (m_seed_mode || is_seed()) + { + // once we're a seed and remove the piece picker, we stop tracking + // piece- and file priority. We consider everything as being + // "wanted" + st.total_done = m_torrent_file->total_size() - m_padding_bytes; + st.total_wanted_done = m_size_on_disk; + st.total_wanted = m_size_on_disk; + TORRENT_ASSERT(st.total_wanted <= st.total_done); + TORRENT_ASSERT(st.total_wanted_done <= st.total_wanted); + TORRENT_ASSERT(st.total_done <= m_torrent_file->total_size()); + return; + } + else if (!has_picker()) + { + st.total_done = 0; + st.total_wanted_done = 0; + st.total_wanted = m_size_on_disk; + return; + } + + TORRENT_ASSERT(has_picker()); + + file_storage const& files = m_torrent_file->files(); + + st.total_wanted = std::min(m_size_on_disk, calc_bytes(files, m_picker->want())); + st.total_wanted_done = std::min(m_file_progress.total_on_disk() + , 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)); + + auto const additional_bytes = std::int64_t(i->finished + i->writing + - m_picker->pad_bytes_in_piece(index) / block_size()) + * block_size(); + TORRENT_ASSERT(additional_bytes >= 0); + st.total_done += additional_bytes; + if (m_picker->piece_priority(index) > dont_download) + st.total_wanted_done += additional_bytes; + } + + TORRENT_ASSERT(st.total_wanted_done >= 0); + TORRENT_ASSERT(st.total_done >= st.total_wanted_done); + } + + void torrent::on_piece_verified(aux::vector block_hashes + , 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; + + m_picker->completed_hash_job(piece); + + boost::tribool passed = boost::indeterminate; + boost::tribool v2_passed = boost::indeterminate; + + if (settings().get_bool(settings_pack::disable_hash_checks)) + { + passed = v2_passed = true; + } + else if (error) + { + passed = v2_passed = false; + } + else + { + if (torrent_file().info_hashes().has_v1()) + { + passed = sha1_hash(piece_hash) == m_torrent_file->hash_for_piece(piece); + } + + if (!block_hashes.empty()) + { + TORRENT_ASSERT(torrent_file().info_hashes().has_v2()); + v2_passed = on_blocks_hashed(piece, block_hashes); + } + } + + if (!error && ((passed && !v2_passed) || (!passed && v2_passed))) + { + set_error(errors::torrent_inconsistent_hashes, torrent_status::error_file_none); + pause(); + return; + } + + bool const disk_error = (!passed || !v2_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 || v2_passed) ? "passed" + : disk_error ? "disk failed" + : (!passed || !v2_passed) ? "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 || v2_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 if (!passed || !v2_passed) + { + // 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 when either: + // * we have completely downloaded piece 'index' and its hash has been verified. + // * during initial file check when we find a piece whose hash is correct + // * if there's a pad-file that extends over the entire piece + // this function does not update the piece picker, telling it we have the + // piece, it just does all the torrent-level accounting that needs to + // happen. It may not be called twice for the same piece (if it is, + // file_progress will assert) + 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(); + update_want_tick(); + + 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 + } + update_want_tick(); + } + + boost::tribool torrent::on_blocks_hashed(piece_index_t const piece + , span const block_hashes) + { + boost::tribool ret = boost::indeterminate; + need_hash_picker(); + + int const blocks_in_piece = torrent_file().orig_files().blocks_in_piece2(piece); + int const blocks_per_piece = torrent_file().orig_files().piece_length() / default_block_size; + + // the blocks are guaranteed to represent exactly one piece + TORRENT_ASSERT(blocks_in_piece == int(block_hashes.size())); + + TORRENT_ALLOCA(block_passed, bool, blocks_in_piece); + std::fill(block_passed.begin(), block_passed.end(), false); + + set_block_hash_result last_result = set_block_hash_result(set_block_hash_result::result::unknown); + + for (int i = 0; i < blocks_in_piece; ++i) + { + // if there was an enoent or eof error the block hashes array may be incomplete + // bail if we've hit the end of the valid hashes + if (block_hashes[i].is_all_zeros()) return false; + + auto const result = get_hash_picker().set_block_hash(piece + , i * default_block_size, block_hashes[i]); + last_result = result; + + if (result.status == set_block_hash_result::result::success) + { + TORRENT_ASSERT(result.first_verified_block < blocks_in_piece); + TORRENT_ASSERT(blocks_in_piece <= blocks_per_piece); + + // all verified ranges should always be full pieces or less + TORRENT_ASSERT(result.first_verified_block >= 0 + || (result.first_verified_block % blocks_per_piece) == 0); + TORRENT_ASSERT(result.num_verified <= blocks_per_piece + || (result.num_verified % blocks_per_piece) == 0); + + // note that result.num_verified may cover pad blocks too, and + // so may be > blocks_in_piece + + // sometimes, completing a single block may "unlock" validating + // multiple pieces. e.g. if we don't have the piece layer yet, + // but we completed the last block in the whole torrent, now we + // can validate everything. For this reason, + // first_verified_block may be negative. + + // In this call, we track the blocks in this piece though, in + // the block_passed array. For that tracking we need to clamp + // the start index to 0. + auto const first_block = std::max(0, result.first_verified_block); + auto const count = std::min(blocks_in_piece - first_block, result.num_verified); + std::fill_n(block_passed.begin() + first_block, count, true); + + // the current block (i) should be part of the range that was + // verified + TORRENT_ASSERT(first_block <= i); + TORRENT_ASSERT(i < first_block + count); + + using delta = piece_index_t::diff_type; + + // if the hashes for more than one piece have been verified, + // check for any pieces which were already checked but couldn't + // be verified and mark them as verified + for (piece_index_t verified_piece = piece + delta(result.first_verified_block / blocks_per_piece) + , end = verified_piece + delta(result.num_verified / blocks_per_piece) + ; verified_piece < end; ++verified_piece) + { + if (!has_picker() + || verified_piece == piece + || !m_picker->is_piece_finished(verified_piece) + || m_picker->has_piece_passed(verified_piece)) + continue; + + TORRENT_ASSERT(get_hash_picker().piece_verified(verified_piece)); + m_picker->we_have(verified_piece); + update_gauge(); + we_have(verified_piece); + } + } + else if (result.status == set_block_hash_result::result::block_hash_failed) + { + ret = false; + } + } + if (last_result.status == set_block_hash_result::result::piece_hash_failed) + { + // only if the *last* block causes the piece to fail, do we know + // it actually failed. Otherwise it might have been failing + // because of other, previously existing block hashes. + ret = false; + } + + if (boost::indeterminate(ret) && std::all_of(block_passed.begin(), block_passed.end() + , [](bool e) { return e; })) + { + ret = true; + } + return ret; + } + + // 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)", int(index)); +#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); + } + + // increase the trust point of all peers that sent + // parts of this piece. + std::set const peers = [&] + { + std::vector const downloaders = m_picker->get_downloaders(index); + + std::set ret; + // 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(ret, ret.begin()), static_cast(nullptr)); + return ret; + }(); + + 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); + } + } + + m_picker->piece_passed(index); + update_gauge(); + we_have(index); + } + +#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 + + // blocks may contain the block indices of the blocks that failed (if this is + // a v2 torrent). + void torrent::piece_failed(piece_index_t const index, std::vector blocks) + { + // 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(has_picker()); + TORRENT_ASSERT(index >= piece_index_t(0)); + TORRENT_ASSERT(index < m_torrent_file->end_piece()); + TORRENT_ASSERT(std::is_sorted(blocks.begin(), blocks.end())); + + 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 + + std::vector const downloaders = m_picker->get_downloaders(index); + + // decrease the trust point of all peers that sent + // parts of this piece. + // first, build a set of all peers that participated + // if we know which blocks failed, just include the peer(s) sending those + // blocks + std::set const peers = [&] + { + std::set ret; + if (!blocks.empty() && !downloaders.empty()) + { + for (auto const b : blocks) ret.insert(downloaders[std::size_t(b)]); + } + else + { + std::copy(downloaders.begin(), downloaders.end(), std::inserter(ret, ret.begin())); + } + return ret; + }(); + + // if this piece wasn't downloaded from peers, we just found it on disk. + // In that case, we should just consider it as "not-have" and there's no + // need to try to get higher fidelity hashes (yet) + bool const found_on_disk = peers.size() == 1 && peers.count(nullptr); + + if (!torrent_file().info_hashes().has_v1() && blocks.empty() && !found_on_disk) + { + // TODO: only do this if the piece size > 1 blocks + // This is a v2 torrent so we can request get block + // level hashes. + verify_block_hashes(index); + } + + // the below code is penalizing peers that sent use bad data. + // increase the total amount of failed bytes + if (!found_on_disk) + { + if (blocks.empty()) + add_failed_bytes(m_torrent_file->piece_size(index)); + else + add_failed_bytes(static_cast(blocks.size()) * default_block_size); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& ext : m_extensions) + { + ext->on_piece_failed(index); + } +#endif + + // did we receive this piece from a single peer? + // if we know exactly which blocks failed the hash, we can also be certain + // that all peers in the list sent us bad data + bool const known_bad_peer = (!found_on_disk && peers.size() == 1) || !blocks.empty(); + + penalize_peers(peers, index, known_bad_peer); + } + + // 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 + , [self = shared_from_this(), c = std::move(blocks)](piece_index_t const& p) + { self->on_piece_sync(p, c); }); + m_ses.deferred_submit_jobs(); + } + 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, std::move(blocks)); + } + } + + void torrent::penalize_peers(std::set const& peers + , piece_index_t const index, bool const known_bad_peer) + { + 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, known_bad_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 + || (known_bad_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); + } + } + } + } + + 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, std::vector const& blocks) 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, blocks); + + 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; + if (!blocks.empty() + && std::find(blocks.begin(), blocks.end(), b.block.block_index) == blocks.end()) + 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; + if (!blocks.empty() + && std::find(blocks.begin(), blocks.end(), b.block.block_index) == blocks.end()) + 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}; + } + + m_inactivity_timer.cancel(); + +#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()); + } + m_ses.deferred_submit_jobs(); + } + 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 const 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_hashes()); + } + else + { + alerts().emplace_alert(get_handle(), m_torrent_file->info_hashes()); + } + } + 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, m_torrent_file->files().file_path(file_idx), file_idx); + m_torrent_file->rename_file(file_idx, filename); + + set_need_save_resume(); + } + } + 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 + { + auto const self = reinterpret_cast(this); + auto 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 aux::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(); + post(m_ses.get_context(), [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 const prev_prio = m_picker->piece_priority(piece); + bool const was_finished = is_finished(); + bool const filter_updated = m_picker->set_piece_priority(piece, top_priority); + if (prev_prio == dont_download) + { + update_gauge(); + if (filter_updated) update_peer_interest(was_finished); + } + 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 const prev_prio = m_picker->piece_priority(piece); + bool const was_finished = is_finished(); + bool const filter_updated = m_picker->set_piece_priority(piece, top_priority); + if (prev_prio == dont_download) + { + update_gauge(); + if (filter_updated) update_peer_interest(was_finished); + } + + 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 const downloaders + = m_picker->get_downloaders(piece); + + int block = 0; + for (auto i = downloaders.begin() + , end(downloaders.end()); i != end; ++i, ++block) + { + torrent_peer* const 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) + { + update_piece_priorities(prios); + m_file_priority = std::move(prios); + set_need_save_resume(); +#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(); + return; + } + + if (alerts().should_post()) + alerts().emplace_alert(get_handle()); + + 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); + + m_deferred_file_priorities.clear(); + + // 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. + + m_outstanding_file_priority = true; + ADD_OUTSTANDING_ASYNC("file_priority"); + m_ses.disk_thread().async_set_file_priority(m_storage + , std::move(new_priority) + , [self = shared_from_this()] (storage_error const& ec, aux::vector p) + { self->on_file_priority(ec, std::move(p)); }); + m_ses.deferred_submit_jobs(); + } + else + { + m_file_priority = std::move(new_priority); + set_need_save_resume(); + } + } + + 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) + { + m_outstanding_file_priority = true; + ADD_OUTSTANDING_ASYNC("file_priority"); + m_ses.disk_thread().async_set_file_priority(m_storage + , std::move(new_priority) + , [self = shared_from_this()] (storage_error const& ec, aux::vector p) + { self->on_file_priority(ec, std::move(p)); }); + m_ses.deferred_submit_jobs(); + } + else + { + m_file_priority = std::move(new_priority); + set_need_save_resume(); + } + } + + 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(); + } + } + + std::vector torrent::trackers() const + { + std::vector ret; + ret.reserve(m_trackers.size()); + for (auto const& t : m_trackers) + { + ret.emplace_back(t.url); + auto& tr = ret.back(); + tr.source = t.source; + tr.trackerid = t.trackerid; + tr.verified = t.verified; + tr.tier = t.tier; + tr.fail_limit = t.fail_limit; + tr.endpoints.reserve(t.endpoints.size()); + for (auto const& ep : t.endpoints) + { + tr.endpoints.emplace_back(); + auto& aep = tr.endpoints.back(); + aep.local_endpoint = ep.local_endpoint; + aep.enabled = ep.enabled; + + for (protocol_version v : {protocol_version::V1, protocol_version::V2}) + { + aep.info_hashes[v].message = ep.info_hashes[v].message; + aep.info_hashes[v].last_error = ep.info_hashes[v].last_error; + aep.info_hashes[v].next_announce = ep.info_hashes[v].next_announce; + aep.info_hashes[v].min_announce = ep.info_hashes[v].min_announce; + aep.info_hashes[v].scrape_incomplete = ep.info_hashes[v].scrape_incomplete; + aep.info_hashes[v].scrape_complete = ep.info_hashes[v].scrape_complete; + aep.info_hashes[v].scrape_downloaded = ep.info_hashes[v].scrape_downloaded; + aep.info_hashes[v].fails = ep.info_hashes[v].fails; + aep.info_hashes[v].updating = ep.info_hashes[v].updating; + aep.info_hashes[v].start_sent = ep.info_hashes[v].start_sent; + aep.info_hashes[v].complete_sent = ep.info_hashes[v].complete_sent; + aep.info_hashes[v].triggered_manually = ep.info_hashes[v].triggered_manually; +#if TORRENT_ABI_VERSION == 1 + tr.complete_sent |= ep.info_hashes[v].complete_sent; +#endif + } +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/aux_/disable_warnings_push.hpp" + aep.message = aep.info_hashes[protocol_version::V1].message; + aep.scrape_incomplete = ep.info_hashes[protocol_version::V1].scrape_incomplete; + aep.scrape_complete = ep.info_hashes[protocol_version::V1].scrape_complete; + aep.scrape_downloaded = ep.info_hashes[protocol_version::V1].scrape_downloaded; + aep.complete_sent = ep.info_hashes[protocol_version::V1].complete_sent; + aep.last_error = ep.info_hashes[protocol_version::V1].last_error; + aep.fails = ep.info_hashes[protocol_version::V1].fails; + aep.next_announce = ep.info_hashes[protocol_version::V1].next_announce; + aep.min_announce = ep.info_hashes[protocol_version::V1].min_announce; + aep.updating = ep.info_hashes[protocol_version::V1].updating; +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + } + } + return ret; + } + + void torrent::replace_trackers(std::vector const& urls) + { + m_trackers.clear(); + for (auto const& t : urls) + { + if (t.url.empty()) continue; + m_trackers.emplace_back(t); + } + + // make sure the trackers are correctly ordered by tier + std::sort(m_trackers.begin(), m_trackers.end() + , [](aux::announce_entry const& lhs, aux::announce_entry const& rhs) + { return lhs.tier < rhs.tier; }); + + m_last_working_tracker = -1; + + if (settings().get_bool(settings_pack::prefer_udp_trackers)) + prioritize_udp_trackers(); + + if (m_announcing && !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.tier + , [] (int tier, aux::announce_entry const& v) { return tier < v.tier; }); + if (k - m_trackers.begin() < m_last_working_tracker) ++m_last_working_tracker; + k = m_trackers.insert(k, aux::announce_entry(url.url)); + if (url.source == 0) k->source = announce_entry::source_client; + else k->source = url.source; + k->trackerid = url.trackerid; + k->tier = url.tier; + k->fail_limit = url.fail_limit; + set_need_save_resume(); + 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_SSL_PEERS + // 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; + } + + error_code ec; + m_ssl_ctx->set_password_callback( + [passphrase](std::size_t, ssl::context::password_purpose purpose) + { + return purpose == ssl::context::for_reading ? passphrase : ""; + } + , ec); + if (ec) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), ec, ""); + } + m_ssl_ctx->use_certificate_file(certificate, ssl::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, ssl::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()); + + error_code ec; + m_ssl_ctx->use_certificate(certificate_buf, ssl::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, ssl::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); + + using deferred_handler_type = aux::handler< + torrent + , decltype(&torrent::on_remove_peers) + , &torrent::on_remove_peers + , &torrent::on_error + , &torrent::on_exception + , decltype(m_deferred_handler_storage) + , &torrent::m_deferred_handler_storage + >; + static_assert(sizeof(deferred_handler_type) == sizeof(std::shared_ptr) + , "deferred handler does not have the expected size"); + m_deferred_disconnect.post_deferred(m_ses.get_context(), deferred_handler_type(shared_from_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 (!settings().get_bool(settings_pack::allow_idna) && is_idna(hostname)) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + debug_log("IDNA disallowed in web seeds: %s", web->url.c_str()); +#endif + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle() + , web->url, error_code(errors::blocked_by_idna)); + } + // never try it again + remove_web_seed_iter(web); + return; + } + + 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; + } + +#if TORRENT_USE_SSL + 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 + + std::uint16_t const proxy_port = ps.port; + + // use proxy + web->resolving = true; + m_ses.get_resolver().async_resolve(ps.hostname, aux::resolver_interface::abort_on_shutdown + , [self = shared_from_this(), 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, aux::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 (m_ses.is_aborted()) 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 (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, aux::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) + { + // if this is set, we don't allow this web seed to have resolved to a + // local IP + if (web->no_local_ips && !aux::is_global(addr)) continue; + + // 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 (web->endpoints.empty()) + { + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle() + , web->url, errors::banned_by_ip_filter); + } + + // the name lookup failed for the http host. Don't try + // this host again + remove_web_seed_iter(web); + return; + } + + 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 (aux::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; + + void* userdata = nullptr; +#if TORRENT_USE_SSL + const bool ssl = string_begins_no_case("https://", web->url.c_str()); + if (ssl) + { +#ifdef TORRENT_SSL_PEERS + userdata = m_ssl_ctx.get(); +#endif + if (!userdata) userdata = m_ses.ssl_ctx(); + } +#endif + aux::socket_type s = instantiate_connection(m_ses.get_context() + , m_ses.proxy(), userdata, nullptr, true, false); + + if (boost::get(&s)) + { + // the web seed connection will talk immediately to + // the proxy, without requiring CONNECT support + boost::get(s).set_no_connect(true); + } + + std::string hostname; + std::string path; + error_code ec; + using std::ignore; + std::tie(ignore, ignore, hostname, ignore, path) + = 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; + } + + if (!settings().get_bool(settings_pack::allow_idna) && is_idna(hostname)) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + debug_log("IDNA disallowed in web seeds: %s", web->url.c_str()); +#endif + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle() + , web->url, error_code(errors::blocked_by_idna)); + } + // never try it again + remove_web_seed_iter(web); + return; + } + + // The SSRF mitigation for web seeds is that any HTTP server on the + // local network may not use any query string parameters + if (settings().get_bool(settings_pack::ssrf_mitigation) + && aux::is_local(web->peer_info.addr) + && path.find('?') != std::string::npos) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("*** SSRF MITIGATION BLOCKED WEB SEED: %s" + , web->url.c_str()); + } +#endif + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle() + , web->url, errors::ssrf_mitigation); + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle() + , a, peer_blocked_alert::ssrf_mitigation); + // never try it again + remove_web_seed_iter(web); + return; + } + + bool const is_ip = aux::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 + && (boost::get(&s) +#if TORRENT_USE_SSL + || boost::get>(&s) +#endif + )) + { + // we're using a socks proxy and we're resolving + // hostnames through it + socks5_stream& str = +#if TORRENT_USE_SSL + ssl ? boost::get>(s).next_layer() : +#endif + boost::get(s); + + 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_context() + , shared_from_this() + , std::move(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(pack, *web); + } + else if (web->type == web_seed_entry::http_seed) + { + c = std::make_shared(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 + } + + hash_request torrent::pick_hashes(peer_connection* peer) + { + need_hash_picker(); + if (!m_hash_picker) return {}; + return m_hash_picker->pick_hashes(peer->get_bitfield()); + } + + std::vector torrent::get_hashes(hash_request const& req) const + { + TORRENT_ASSERT(m_torrent_file->is_valid()); + if (!m_torrent_file->is_valid()) return {}; + TORRENT_ASSERT(validate_hash_request(req, m_torrent_file->files())); + + auto const& f = m_merkle_trees[req.file]; + + return f.get_hashes(req.base, req.index, req.count, req.proof_layers); + } + + bool torrent::add_hashes(hash_request const& req, span hashes) + { + need_hash_picker(); + if (!m_hash_picker) return true; + add_hashes_result const result = m_hash_picker->add_hashes(req, hashes); + for (auto& p : result.hash_failed) + { + if (torrent_file().info_hashes().has_v1() && have_piece(p.first)) + { + set_error(errors::torrent_inconsistent_hashes, torrent_status::error_file_none); + pause(); + return result.valid; + } + + TORRENT_ASSERT(!have_piece(p.first)); + + // the piece may not have been downloaded in this session + // it should be open for downloading so nothing needs to be done here + if (!m_picker || !m_picker->is_downloading(p.first)) continue; + piece_failed(p.first, p.second); + } + for (piece_index_t p : result.hash_passed) + { + if (torrent_file().info_hashes().has_v1() && !have_piece(p)) + { + set_error(errors::torrent_inconsistent_hashes, torrent_status::error_file_none); + pause(); + return result.valid; + } + + if (m_picker && m_picker->is_downloading(p) && m_picker->is_piece_finished(p) + && !m_picker->is_hashing(p)) + { + piece_passed(p); + } + } + return result.valid; + } + + void torrent::hashes_rejected(hash_request const& req) + { + if (!m_hash_picker) return; + m_hash_picker->hashes_rejected(req); + // we need to poke all of the v2 peers in case there are no other + // outstanding hash requests + for (auto peer : m_connections) + { + if (peer->type() != connection_type::bittorrent) continue; + auto* const btpeer = static_cast(peer); + btpeer->maybe_send_hash_request(); + } + } + + void torrent::verify_block_hashes(piece_index_t index) + { + need_hash_picker(); + if (!m_hash_picker) return; +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("Piece %d hash failure, requesting block hashes", int(index)); + } +#endif + m_hash_picker->verify_block_hashes(index); + } + + std::shared_ptr torrent::get_torrent_file() const + { + if (!m_torrent_file->is_valid()) return {}; + return m_torrent_file; + } + + std::shared_ptr torrent::get_torrent_copy_with_hashes() const + { + if (!m_torrent_file->is_valid()) return {}; + auto ret = std::make_shared(*m_torrent_file); + + if (ret->v2()) + { + aux::vector, file_index_t> v2_hashes; + for (auto const& tree : m_merkle_trees) + { + auto const& layer = tree.get_piece_layer(); + std::vector out_layer; + out_layer.reserve(layer.size() * sha256_hash::size()); + for (auto const& h : layer) + { + // we're missing a piece layer. We can't return a valid + // torrent + if (h.is_all_zeros()) return {}; + out_layer.insert(out_layer.end(), h.data(), h.data() + sha256_hash::size()); + } + v2_hashes.emplace_back(std::move(out_layer)); + } + ret->set_piece_layers(std::move(v2_hashes)); + } + + return ret; + } + + std::vector> torrent::get_piece_layers() const + { + std::vector> ret; + for (auto const& tree : m_merkle_trees) + ret.emplace_back(tree.get_piece_layer()); + return ret; + } + + void torrent::enable_all_trackers() + { + for (aux::announce_entry& ae : m_trackers) + for (aux::announce_endpoint& aep : ae.endpoints) + aep.enabled = true; + } + + void torrent::write_resume_data(resume_data_flags_t const flags, 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 = this->flags(); + + ret.added_time = m_added_time; + ret.completed_time = m_completed_time; + + ret.save_path = m_save_path; + + ret.info_hashes = torrent_file().info_hashes(); + if (m_name) ret.name = *m_name; + +#if TORRENT_ABI_VERSION < 3 + ret.info_hash = ret.info_hashes.get_best(); +#endif + + if (valid_metadata() && (flags & torrent_handle::save_info_dict)) + { + ret.ti = m_torrent_file; + } + + // 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 && dp.writing == 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 + || info[i].state == piece_picker::block_info::state_writing) + 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 (valid_metadata() + && &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 (valid_metadata() && 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 (m_torrent_file->info_hashes().has_v2()) + { + auto const num_files = m_merkle_trees.size(); + ret.merkle_trees.clear(); + ret.merkle_trees.reserve(num_files); + ret.merkle_tree_mask.clear(); + ret.merkle_tree_mask.reserve(num_files); + ret.verified_leaf_hashes.clear(); + ret.verified_leaf_hashes.reserve(num_files); + for (auto const& t : m_merkle_trees) + { + // use stuctured binding in C++17 + aux::vector mask; + std::vector sparse_tree; + std::tie(sparse_tree, mask) = t.build_sparse_vector(); + ret.merkle_trees.emplace_back(std::move(sparse_tree)); + ret.merkle_tree_mask.emplace_back(std::move(mask)); + ret.verified_leaf_hashes.emplace_back(t.verified_leafs()); + } + + if (!has_hash_picker() && !m_have_all) + { + ret.verified_leaf_hashes.reserve(m_torrent_file->files().num_files()); + for (file_index_t f(0); f != m_torrent_file->files().end_file(); ++f) + { + if (m_torrent_file->files().pad_file_at(f)) + { + ret.verified_leaf_hashes.emplace_back(); + continue; + } + ret.verified_leaf_hashes.emplace_back(m_torrent_file->files().file_num_blocks(f), false); + } + } + } + } + +#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())); + block_info* blocks = &blk[std::size_t(counter * blocks_per_piece)]; + pi.blocks = blocks; + 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 = 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; + } + } + + bi.num_peers = info.num_peers; + } + pi.piece_index = i->index; + queue->push_back(pi); + } + + } + +#if defined TORRENT_SSL_PEERS + struct hostname_visitor + { + explicit hostname_visitor(std::string const& h) : hostname_(h) {} + template + void operator()(T&) {} + template + void operator()(ssl_stream& s) { s.set_host_name(hostname_); } + std::string const& hostname_; + }; + + struct ssl_handle_visitor + { + template + ssl::stream_handle_type operator()(T&) + { return nullptr; } + + template + ssl::stream_handle_type operator()(ssl_stream& s) + { return s.handle(); } + }; +#endif + + 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); + + // 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 + aux::utp_socket_manager* sm = nullptr; + +#if TORRENT_USE_I2P + if (peerinfo->is_i2p_addr) + { + 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; + } + } + else +#endif + { + 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; + } + } + + // TODO: come up with a better way of doing this, instead of an + // immediately invoked lambda expression. + aux::socket_type s = [&] { + +#if TORRENT_USE_I2P + if (peerinfo->is_i2p_addr) + { + // 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. + aux::socket_type ret = instantiate_connection(m_ses.get_context() + , m_ses.i2p_proxy(), nullptr, nullptr, false, false); + boost::get(ret).set_destination(static_cast(peerinfo)->dest()); + boost::get(ret).set_command(i2p_stream::cmd_connect); + boost::get(ret).set_session_id(m_ses.i2p_session()); + return ret; + } + else +#endif + { + void* userdata = nullptr; +#ifdef TORRENT_SSL_PEERS + 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 + + aux::socket_type ret = instantiate_connection(m_ses.get_context() + , m_ses.proxy(), userdata, sm, true, false); + +#if defined TORRENT_SSL_PEERS + if (is_ssl_torrent()) + { + // for ssl sockets, set the hostname + std::string const host_name = aux::to_hex( + m_torrent_file->info_hashes().get(peerinfo->protocol())); + + boost::apply_visitor(hostname_visitor{host_name}, ret); + } +#endif + return ret; + } + }(); + + 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_context() + , shared_from_this() + , std::move(s) + , a + , peerinfo + , our_pid + }; + + auto c = std::make_shared(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; + } + + error_code torrent::initialize_merkle_trees() + { + if (!info_hash().has_v2()) return {}; + + bool valid = m_torrent_file->v2_piece_hashes_verified(); + + file_storage const& fs = m_torrent_file->orig_files(); + m_merkle_trees.reserve(fs.num_files()); + for (file_index_t i : fs.file_range()) + { + if (fs.pad_file_at(i) || fs.file_size(i) == 0) + { + m_merkle_trees.emplace_back(); + continue; + } + m_merkle_trees.emplace_back(fs.file_num_blocks(i) + , fs.piece_length() / default_block_size, fs.root_ptr(i)); + auto const piece_layer = m_torrent_file->piece_layer(i); + if (piece_layer.empty()) + { + valid = false; + continue; + } + + if (!m_merkle_trees[i].load_piece_layer(piece_layer)) + { + m_merkle_trees[i] = aux::merkle_tree(); + m_v2_piece_layers_validated = false; + return errors::torrent_invalid_piece_layer; + } + } + + m_v2_piece_layers_validated = valid; + + m_torrent_file->free_piece_layers(); + return {}; + } + + bool torrent::set_metadata(span metadata_buf) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (m_torrent_file->is_valid()) return false; + + if (m_torrent_file->info_hashes().has_v1()) + { + sha1_hash const info_hash = hasher(metadata_buf).final(); + if (info_hash != m_torrent_file->info_hashes().v1) + { + // check if the v1 hash is a truncated v2 hash + sha256_hash const info_hash2 = hasher256(metadata_buf).final(); + if (sha1_hash(info_hash2.data()) != m_torrent_file->info_hashes().v1) + { + if (alerts().should_post()) + { + alerts().emplace_alert(get_handle() + , errors::mismatching_info_hash); + } + return false; + } + } + } + if (m_torrent_file->info_hashes().has_v2()) + { + // we don't have to worry about computing the v2 hash twice because + // if the v1 hash was a truncated v2 hash then the torrent_file should + // not have a v2 hash and we shouldn't get here + sha256_hash const info_hash = hasher256(metadata_buf).final(); + if (info_hash != m_torrent_file->info_hashes().v2) + { + if (alerts().should_post()) + { + alerts().emplace_alert(get_handle() + , errors::mismatching_info_hash); + } + return false; + } + } + + // the torrent's info hash might change + // e.g. it could be a hybrid torrent which we only had one of the hashes for + // so remove the existing entry + info_hash_t const old_ih = m_torrent_file->info_hashes(); + + error_code ec; + int pos = 0; + bdecode_node const metadata = bdecode(metadata_buf, ec, &pos, 200 + , settings().get_int(settings_pack::metadata_token_limit)); + + if (ec || !m_torrent_file->parse_info_section(metadata, ec + , settings().get_int(settings_pack::max_piece_count))) + { + 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; + } + + // now, we might already have this torrent in the session. + m_torrent_file->info_hashes().for_each([&](sha1_hash const& ih, protocol_version) + { + auto t = m_ses.find_torrent(info_hash_t(ih)).lock(); + if (t && t != shared_from_this()) + { + // 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_metadata); + abort(); + } + }); + + if (m_abort) return false; + + m_info_hash = m_torrent_file->info_hashes(); + + m_size_on_disk = aux::size_on_disk(m_torrent_file->files()); + + m_ses.update_torrent_info_hash(shared_from_this(), old_ih); + + ec = initialize_merkle_trees(); + if (ec) + { + set_error(ec, torrent_status::error_file_metadata); + 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_SSL_PEERS + if (is_ssl_torrent()) + { + // if this is an SSL torrent, don't allow non SSL peers on it + aux::socket_type& s = p->get_socket(); + + auto stream_handle = boost::apply_visitor(ssl_handle_visitor{}, s); + + if (!stream_handle) + { + // 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::has_context(stream_handle, ssl::get_handle(*m_ssl_ctx))) + { + // if the SSL context 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; + } + } +#else // TORRENT_SSL_PEERS + 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_SSL_PEERS + + 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) + { + 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; + + if (!settings().get_bool(settings_pack::enable_outgoing_tcp) + && !settings().get_bool(settings_pack::enable_outgoing_utp)) + 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 : aux::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_want_tick(); + 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() && p->can_disconnect(errors::torrent_finished)) + { +#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)); + m_ses.deferred_submit_jobs(); + } + + // 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); + + // 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_hash_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.enabled) continue; + for (auto& a : aep.info_hashes) + { + if (a.complete_sent) continue; + a.next_announce = now; + a.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) + { + for (auto& aep : t.endpoints) + { + for (auto& a : aep.info_hashes) + a.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(); + } + + aux::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)); + m_ses.deferred_submit_jobs(); + } + + 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, m_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; + m_ses.deferred_submit_jobs(); + } + else + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), save_path, m_save_path); + +#if TORRENT_USE_UNC_PATHS + m_save_path = canonicalize_path(save_path); +#else + + m_save_path = save_path; +#endif + set_need_save_resume(); + } + } + + 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); + 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.v1 == m_torrent_file->info_hashes().v1); + TORRENT_ASSERT(m_info_hash.v2 == m_torrent_file->info_hashes().v2); + } + + 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) + { + 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()) + { + if (p.peer_info_struct()->seed) + { + ++seeds; + } + else + { + TORRENT_ASSERT(!p.is_seed()); + } + } + + 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()); + // TODO: perhaps 0 should actially mean 0 + 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()); + // TODO: perhaps 0 should actially mean 0 + 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()); + if (limit <= 0 || limit == aux::bandwidth_channel::inf) 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; + m_ses.deferred_submit_jobs(); + 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 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 (file == torrent_status::error_file_metadata) return "metadata"; + + 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 we're currently seeding and using tracker supplied scrape + // data, we should remove ourselves from the seed count + int const self_seed = is_seed() && !is_paused() ? 1 : 0; + + if (m_complete != 0xffffff) seeds = std::max(0, int(m_complete) - self_seed); + 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 (m_abort) + { + alerts().emplace_alert(get_handle() + , errors::torrent_removed); + 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; + state_updated(); + + if ((flags & torrent_handle::flush_disk_cache) && m_storage) + { + m_ses.disk_thread().async_release_files(m_storage); + m_ses.deferred_submit_jobs(); + } + + state_updated(); + + add_torrent_params atp; + write_resume_data(flags, 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)); + m_ses.deferred_submit_jobs(); + } + + 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, bool const was_paused) + { + 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(); + + // do_paused() may be called twice, if the first time is to enter + // graceful pause, and the second time proper pause. We can only update + // these timers once, otherwise they'll be inflated + if (!was_paused) + { + 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 + , [self = shared_from_this()] { self->on_torrent_paused(); }); + m_ses.deferred_submit_jobs(); + } + 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); + ent.no_local_ips = bool(flags & no_local_ips); + + // 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.emplace_back(std::move(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(flags, true); + } + 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); + 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 + && 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 (m_state == torrent_status::checking_files) return; + + start_announcing(); + + do_connect_boost(); + } + + namespace + { + struct timer_state + { + explicit timer_state(aux::listen_socket_handle s) + : socket(std::move(s)) {} + + aux::listen_socket_handle socket; + + struct state_t + { + int tier = INT_MAX; + bool found_working = false; + bool done = false; + }; + aux::array state; + }; + } + + 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; + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-braces" +#endif + aux::array const supports_protocol{ + { + m_info_hash.has_v1(), + m_info_hash.has_v2() + }}; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + 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& ep_state = *aep_state_iter; + + if (!aep.enabled) continue; + for (protocol_version const ih : all_versions) + { + if (!supports_protocol[ih]) continue; + + auto& state = ep_state.state[ih]; + auto& a = aep.info_hashes[ih]; + + if (state.done) 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, a.is_working() + , a.fails, t.fail_limit, a.updating); + } +#endif + + 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 (a.is_working()) { state.tier = t.tier; state.found_working = false; } + if (a.fails >= t.fail_limit && t.fail_limit != 0) continue; + if (a.updating) + { + state.found_working = true; + } + else + { + time_point32 const next_tracker_announce = std::max(a.next_announce, a.min_announce); + if (next_tracker_announce < next_announce + && (!state.found_working || a.is_working())) + next_announce = next_tracker_announce; + } + if (a.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() + , [supports_protocol](timer_state const& s) { + for (protocol_version const ih : all_versions) + { + if (supports_protocol[ih] && !s.state[ih].done) + return false; + } + return true; + })) + break; + } + +#ifndef TORRENT_DISABLE_LOGGING + bool before_now = false; + bool none_eligible = false; +#endif + if (next_announce <= now) + { + next_announce = now; +#ifndef TORRENT_DISABLE_LOGGING + before_now = true; +#endif + } + else if (next_announce == time_point32::max()) + { + // if no tracker can be announced to, check again in a minute + next_announce = now + minutes32(1); +#ifndef TORRENT_DISABLE_LOGGING + none_eligible = true; +#endif + } + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** update tracker timer: " + "before_now: %d " + "none_eligible: %d " + "m_waiting_tracker: %d " + "next_announce_in: %d" + , before_now + , none_eligible + , 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.expiry() == next_announce) return; + + m_tracker_timer.expires_at(next_announce); + ADD_OUTSTANDING_ASYNC("tracker::on_tracker_announce"); + ++m_waiting_tracker; + m_tracker_timer.async_wait([self = shared_from_this()](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 (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; + + m_tracker_timer.cancel(); + + m_announcing = false; + + time_point32 const now = aux::time_now32(); + for (auto& t : m_trackers) + { + for (auto& aep : t.endpoints) + { + for (auto& a : aep.info_hashes) + { + a.next_announce = now; + a.min_announce = now; + } + } + } + announce_with_tracker(event_t::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(), [](partial_piece_info const& lhs, partial_piece_info const& rhs) + { return lhs.piece_index < rhs.piece_index;; }); + + 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 TORRENT_ABI_VERSION <= 2 + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle(), tick_interval_ms, m_stat); +#endif + + 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_after(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_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 flags) + { + TORRENT_ASSERT(is_single_thread()); + + TORRENT_ASSERT(info_hash().has_v2() || !(flags & pex_lt_v2)); + +#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; + } + + if (!torrent_file().info_hashes().has_v1()) + flags |= pex_lt_v2; + + 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 (bool(p->seed) == s) return; + 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) + { + TORRENT_ASSERT(m_storage); + TORRENT_ASSERT(!m_picker->is_hashing(piece)); + + // we just completed the piece, it should be flushed to disk + disk_job_flags_t flags{}; + + auto const write_mode = settings().get_int(settings_pack::disk_io_write_mode); + if (write_mode == settings_pack::write_through) + flags |= disk_interface::flush_piece; + else if (write_mode == settings_pack::disable_os_cache) + flags |= disk_interface::flush_piece | disk_interface::volatile_read; + if (torrent_file().info_hashes().has_v1()) + flags |= disk_interface::v1_hash; + + aux::vector hashes; + if (torrent_file().info_hashes().has_v2()) + { + hashes.resize(torrent_file().orig_files().blocks_in_piece2(piece)); + } + + if (settings().get_bool(settings_pack::disable_hash_checks)) + { + // short-circuit the hash check if it's disabled + m_picker->started_hash_job(piece); + on_piece_verified(std::move(hashes), piece, sha1_hash(), storage_error{}); + return; + } + + span v2_span(hashes); + m_ses.disk_thread().async_hash(m_storage, piece, v2_span, flags + , [self = shared_from_this(), hashes = std::move(hashes)] + (piece_index_t p, sha1_hash const& h, storage_error const& error) mutable + { self->on_piece_verified(std::move(hashes), p, h, error); }); + m_picker->started_hash_job(piece); + m_ses.deferred_submit_jobs(); + } + + aux::announce_entry* torrent::find_tracker(std::string const& url) + { + auto i = std::find_if(m_trackers.begin(), m_trackers.end() + , [&url](aux::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]) / float(file_size); + } + } +#endif +#endif // TORRENT_ABI_VERSION + + void torrent::file_progress(aux::vector& fp, file_progress_flags_t 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; + + if (!has_picker()) + return; + + 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_hashes = info_hash(); +#if TORRENT_ABI_VERSION < 3 + st->info_hash = info_hash().get_best(); +#endif +#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.expiry() < now) + st->next_announce = seconds(0); + else + st->next_announce = m_tracker_timer.expiry() - 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 + { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-braces" +#endif + aux::array const supports_protocol{ + { + m_info_hash.has_v1(), + m_info_hash.has_v2() + }}; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + for (auto const& t : m_trackers) + { + if (std::any_of(t.endpoints.begin(), t.endpoints.end() + , [supports_protocol](aux::announce_endpoint const& aep) { + for (protocol_version const ih : all_versions) + { + if (supports_protocol[ih] && aep.info_hashes[ih].updating) + return false; + } + return true; + })) 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 = float(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 = float(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, operation_t const op, std::string const& msg + , seconds32 const retry_interval) + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + +// some older versions of clang had a bug where it would fire this warning here +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-braces" +#endif + aux::array const supports_protocol + { { + m_info_hash.has_v1(), + m_info_hash.has_v2() + } }; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("*** tracker error: (%d) %s [%s] %s", ec.value() + , ec.message().c_str(), operation_name(op), msg.c_str()); + } +#endif + if (!(r.kind & tracker_request::scrape_request)) + { + // announce request + aux::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() + , [&](aux::announce_endpoint const& e) { return e.socket == r.outgoing_socket; }); + + if (aep != ae->endpoints.end()) + { + protocol_version const hash_version = r.info_hash == m_info_hash.v1 + ? protocol_version::V1 : protocol_version::V2; + auto& a = aep->info_hashes[hash_version]; + local_endpoint = aep->local_endpoint; + a.failed(settings().get_int(settings_pack::tracker_backoff) + , retry_interval); + a.last_error = ec; + a.message = msg; + fails = a.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(), a.fails); +#endif + if (ec == boost::system::errc::address_family_not_supported) + { + // don't try to announce from this endpoint again + 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() + , [&](aux::announce_endpoint const& ep) + { + for (protocol_version const ih : all_versions) + if (supports_protocol[ih] && ep.info_hashes[ih].is_working()) + return false; + return true; + })) + { + 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, op, ec, msg); + } + } + else + { + aux::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 == event_t::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..ba3fc26 --- /dev/null +++ b/src/torrent_handle.cpp @@ -0,0 +1,922 @@ +/* + +Copyright (c) 2003-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2017, 2019, Alden Torres +Copyright (c) 2017, Falcosc +Copyright (c) 2017, 2020, AllSeeingEyeTolledEweSew +Copyright (c) 2018, Steven Siloti +Copyright (c) 2019, ghbplayer +Copyright (c) 2019, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/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/aux_/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" +#include "libtorrent/aux_/ip_helpers.hpp" // for is_v6 + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/peer_info.hpp" // for peer_list_entry +#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 file_progress_flags_t torrent_handle::piece_granularity; + + 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; + + void block_info::set_peer(tcp::endpoint const& ep) + { + is_v6_addr = aux::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 block_info::peer() const + { + if (is_v6_addr) + return {address_v6(addr.v6), port}; + else + return {address_v4(addr.v4), port}; + } + +#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()); + dispatch(ses.get_context(), [=,&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; + dispatch(ses.get_context(), [=,&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; + dispatch(ses.get_context(), [=,&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().get_best() : sha1_hash{}; + } + + info_hash_t torrent_handle::info_hashes() const + { + std::shared_ptr t = m_torrent.lock(); + return t ? t->info_hash() : info_hash_t(); + } + + 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)); + } +#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&, client_data_t)> const& ext + , client_data_t 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_SSL_PEERS + 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_SSL_PEERS + 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, file_progress_flags_t flags) const + { + auto& arg = static_cast&>(progress); + sync_call(&torrent::file_progress, std::ref(arg), flags); + } + + std::vector torrent_handle::file_progress(file_progress_flags_t flags) const + { + aux::vector ret; + sync_call(&torrent::file_progress, std::ref(ret), flags); + return std::move(ret); + } + + 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); + } + + 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_file); + } + + std::shared_ptr torrent_handle::torrent_file_with_hashes() const + { + return sync_call_ret>( + std::shared_ptr(), &torrent::get_torrent_copy_with_hashes); + } + + std::vector> torrent_handle::piece_layers() const + { + return sync_call_ret>>({} + , &torrent::get_piece_layers); + } + +#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, resume_data_flags_t{}, 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_lsd_announce() const + { + async_call(&torrent::lsd_announce); + } + + 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); + } + + std::vector torrent_handle::get_download_queue() const + { + std::vector queue; + sync_call(&torrent::get_download_queue, &queue); + return queue; + } + + 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)); + } + + client_data_t torrent_handle::userdata() const + { + std::shared_ptr t = m_torrent.lock(); + return t ? t->get_userdata() : client_data_t{}; + } + + bool torrent_handle::in_session() const + { return !sync_call_ret(false, &torrent::is_aborted); } + + 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..ba55248 --- /dev/null +++ b/src/torrent_info.cpp @@ -0,0 +1,1898 @@ +/* + +Copyright (c) 2003-2020, Arvid Norberg +Copyright (c) 2016-2017, Pavel Pimenov +Copyright (c) 2016-2019, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2016, 2019, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/open_mode.hpp" +#include "libtorrent/utf8.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/aux_/escape_string.hpp" // maybe_url_encode +#include "libtorrent/aux_/merkle.hpp" // for merkle_* +#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" +#include "libtorrent/aux_/file_pointer.hpp" +#include "libtorrent/disk_interface.hpp" // for default_block_size +#include "libtorrent/span.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libtorrent { + + TORRENT_EXPORT from_span_t from_span; + + constexpr torrent_info_flags_t torrent_info::multifile; + constexpr torrent_info_flags_t torrent_info::private_torrent; + constexpr torrent_info_flags_t torrent_info::i2p; + constexpr torrent_info_flags_t torrent_info::ssl_torrent; + constexpr torrent_info_flags_t torrent_info::v2_has_piece_hashes; + + namespace { + + 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 + +namespace aux { + + // 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::min(std::size_t(len), ptr.size())); + + // 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; + } + + bool extract_single_file2(bdecode_node const& dict, file_storage& files + , std::string const& path, string_view const name + , std::ptrdiff_t const info_offset, char const* info_buffer + , error_code& ec) + { + if (dict.type() != bdecode_node::dict_t) return false; + + file_flags_t file_flags = get_file_attributes(dict); + + if (file_flags & file_storage::flag_pad_file) + { + ec = errors::torrent_invalid_pad_file; + return false; + } + + // 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 a file is too big, it will cause integer overflow in our + // calculations of the size of the merkle tree (which is all 'int' + // indices) + if (file_size < 0 + || (file_size / default_block_size) >= std::numeric_limits::max() / 2 + || file_size > file_storage::max_file_size) + { + ec = errors::torrent_invalid_length; + return false; + } + + std::time_t const mtime = std::time_t(dict.dict_find_int_value("mtime", 0)); + + char const* pieces_root = nullptr; + + std::string symlink_path; + if (file_flags & file_storage::flag_symlink) + { + if (bdecode_node const s_p = dict.dict_find_list("symlink path")) + { + auto const preallocate = static_cast(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(); + aux::sanitize_append_path_element(symlink_path, pe); + } + } + } + + if (symlink_path.empty() && file_size > 0) + { + bdecode_node const root = dict.dict_find_string("pieces root"); + if (!root || root.type() != bdecode_node::string_t + || root.string_length() != sha256_hash::size()) + { + ec = errors::torrent_missing_pieces_root; + return false; + } + pieces_root = info_buffer + (root.string_offset() - info_offset); + if (sha256_hash(pieces_root).is_all_zeros()) + { + ec = errors::torrent_missing_pieces_root; + return false; + } + } + + files.add_file_borrow(ec, name, path, file_size, file_flags, nullptr + , mtime, symlink_path, pieces_root); + return !ec; + } + + // '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_offset + , char const* info_buffer, bool top_level, 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 a file is too big, it will cause integer overflow in our + // calculations of the size of the merkle tree (which is all 'int' + // indices) + if (file_size < 0 + || (file_size / default_block_size) >= std::numeric_limits::max() / 2 + || file_size > file_storage::max_file_size) + { + 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 = { info_buffer + (p.string_offset() - info_offset) + , static_cast(p.string_length())}; + + while (!filename.empty() && filename.front() == TORRENT_SEPARATOR) + filename.remove_prefix(1); + + aux::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 = {info_buffer + (e.string_offset() - info_offset) + , static_cast(e.string_length()) }; + while (!filename.empty() && filename.front() == TORRENT_SEPARATOR) + filename.remove_prefix(1); + } + aux::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[20]; + std::snprintf(cnt, sizeof(cnt), "%" PRIu64, file_size); + path = combine_path(".pad", 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 = info_buffer + (fh.string_offset() - info_offset); + + std::string symlink_path; + if (file_flags & file_storage::flag_symlink) + { + if (bdecode_node const s_p = dict.dict_find_list("symlink path")) + { + auto const preallocate = static_cast(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(); + aux::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(ec, filename, path, file_size, file_flags, filehash + , mtime, symlink_path); + return !ec; + } + + bool extract_files2(bdecode_node const& tree, file_storage& target + , std::string const& root_dir, ptrdiff_t const info_offset + , char const* info_buffer + , bool const has_files, int const depth, error_code& ec) + { + if (tree.type() != bdecode_node::dict_t) + { + ec = errors::torrent_file_parse_failed; + return false; + } + + // since we're parsing this recursively, we have to be careful not to blow + // up the stack. 100 levels of sub directories should be enough. This + // could be improved by an iterative parser, keeping the state on a more + // compact side-stack + if (depth > 100) + { + ec = errors::torrent_file_parse_failed; + return false; + } + + for (int i = 0; i < tree.dict_size(); ++i) + { + auto e = tree.dict_at_node(i); + if (e.second.type() != bdecode_node::dict_t || e.first.string_value().empty()) + { + ec = errors::torrent_file_parse_failed; + return false; + } + + string_view filename = { info_buffer + (e.first.string_offset() - info_offset) + , static_cast(e.first.string_length()) }; + while (!filename.empty() && filename.front() == TORRENT_SEPARATOR) + filename.remove_prefix(1); + + bool const leaf_node = e.second.dict_size() == 1 && e.second.dict_at(0).first.empty(); + bool const single_file = leaf_node && !has_files && tree.dict_size() == 1; + + std::string path = single_file ? std::string() : root_dir; + aux::sanitize_append_path_element(path, filename); + + if (leaf_node) + { + 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 = {}; + } + + if (!extract_single_file2(e.second.dict_at(0).second, target + , path, filename, info_offset, info_buffer, ec)) + { + return false; + } + continue; + } + + if (!extract_files2(e.second, target, path, info_offset, info_buffer + , true, depth + 1, ec)) + { + return false; + } + } + + 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_offset + , char const* info_buffer, error_code& ec) + { + if (list.type() != bdecode_node::list_t) + { + ec = errors::torrent_file_parse_failed; + return false; + } + target.reserve(list.list_size()); + + for (int i = 0, end(list.list_size()); i < end; ++i) + { + if (!extract_single_file(list.list_at(i), target, root_dir + , info_offset, info_buffer, false, 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(); +#ifdef TORRENT_WINDOWS + aux::file_pointer f(::_wfopen(convert_to_native_path_string(filename).c_str(), L"rb")); +#else + aux::file_pointer f(std::fopen(filename.c_str(), "rb")); +#endif + if (f.file() == nullptr) + { + ec.assign(errno, generic_category()); + return -1; + } + + if (std::fseek(f.file(), 0, SEEK_END) < 0) + { + ec.assign(errno, generic_category()); + return -1; + } + std::int64_t const s = std::ftell(f.file()); + if (s < 0) + { + ec.assign(errno, generic_category()); + return -1; + } + if (s > max_buffer_size) + { + ec = errors::metadata_too_large; + return -1; + } + if (std::fseek(f.file(), 0, SEEK_SET) < 0) + { + ec.assign(errno, generic_category()); + return -1; + } + v.resize(std::size_t(s)); + if (s == 0) return 0; + std::size_t const read = std::fread(v.data(), 1, v.size(), f.file()); + if (read != std::size_t(s)) + { + if (std::feof(f.file())) + { + v.resize(read); + return 0; + } + ec.assign(errno, generic_category()); + return -1; + } + return 0; + } + +} // anonymous namespace + + web_seed_entry::web_seed_entry(std::string url_, type_t type_ + , std::string auth_ + , headers_t extra_headers_) + : url(std::move(url_)) + , auth(std::move(auth_)) + , extra_headers(std::move(extra_headers_)) + , type(std::uint8_t(type_)) + { + } + +TORRENT_VERSION_NAMESPACE_3 + + torrent_info::torrent_info(torrent_info const&) = default; + torrent_info& torrent_info::operator=(torrent_info&&) = default; + + 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 + // 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, load_torrent_limits{}.max_pieces)) + aux::throw_ex(ec); +#else + parse_torrent_file(e, ec, load_torrent_limits{}.max_pieces); +#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, load_torrent_limits{}.max_pieces)) + 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, load_torrent_limits{}.max_pieces)) + 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, load_torrent_limits{}.max_pieces)) + 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; + } +#endif + + file_storage const& torrent_info::orig_files() const + { + TORRENT_ASSERT(is_loaded()); + return m_orig_files ? *m_orig_files : m_files; + } + + void torrent_info::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); + } + + torrent_info::torrent_info(bdecode_node const& torrent_file + , error_code& ec) + { + parse_torrent_file(torrent_file, ec, load_torrent_limits{}.max_pieces); + 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, load_torrent_limits{}.max_pieces); + + 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, load_torrent_limits{}.max_pieces); + + INVARIANT_CHECK; + } + + // 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(info_hash_t const& info_hash) + : m_info_hash(info_hash) + {} + + torrent_info::~torrent_info() = default; + + // internal + void torrent_info::set_piece_layers(aux::vector, file_index_t> pl) + { + m_piece_layers = pl; + m_flags |= v2_has_piece_hashes; + } + + 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)); + } + +#if TORRENT_ABI_VERSION <= 2 + void torrent_info::swap(torrent_info& ti) + { + INVARIANT_CHECK; + + torrent_info tmp = std::move(ti); + ti = std::move(*this); + *this = std::move(tmp); + } + + boost::shared_array torrent_info::metadata() const + { + boost::shared_array ret(new char[std::size_t(m_info_section_size)]); + std::memcpy(ret.get(), m_info_section.get(), std::size_t(m_info_section_size)); + return ret; + } +#endif + + string_view torrent_info::ssl_cert() const + { + if (!(m_flags & ssl_torrent)) 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"); + } + +#if TORRENT_ABI_VERSION < 3 + bool torrent_info::parse_info_section(bdecode_node const& info, error_code& ec) + { + return parse_info_section(info, ec, 0x200000); + } +#endif + + 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.v1 = hasher(section).final(); + m_info_hash.v2 = hasher256(section).final(); + if (info.data_section().size() >= std::numeric_limits::max()) + { + ec = errors::metadata_too_large; + return false; + } + + if (section.empty() || section[0] != 'd' || section[section.size() - 1] != 'e') + { + ec = errors::invalid_bencoding; + 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)); + + // this is the offset from the start of the torrent file buffer to the + // info-dictionary (within the torrent file). + // we need this because we copy just the info dictionary buffer and pull + // out parsed data (strings) from the bdecode_node and need to make them + // point into our copy of the buffer. + std::ptrdiff_t const info_offset = info.data_offset(); + + // check for a version key + int const version = int(info.dict_find_int_value("meta version", -1)); + if (version > 0) + { + char error_string[200]; + if (info.has_soft_error(error_string)) + { + ec = errors::invalid_bencoding; + return false; + } + + if (version > 2) + { + ec = errors::torrent_unknown_version; + return false; + } + } + + if (version < 2) + { + // this is a v1 torrent so the v2 info hash has no meaning + // clear it just to make sure no one tries to use it + m_info_hash.v2.clear(); + } + + // extract piece length + std::int64_t const piece_length = info.dict_find_int_value("piece length", -1); + // limit the piece length at INT_MAX / 2 to get a bit of headroom. We + // commonly compute the number of blocks per pieces by adding + // block_size - 1 before dividing by block_size. That would overflow with + // a piece size of INT_MAX. This limit is still an unreasonably large + // piece size anyway. + if (piece_length <= 0 || piece_length > std::numeric_limits::max() / 2) + { + ec = errors::torrent_missing_piece_length; + return false; + } + + // according to BEP 52: "It must be a power of two and at least 16KiB." + if (version > 1 && (piece_length < default_block_size + || (piece_length & (piece_length - 1)) != 0)) + { + 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; + aux::sanitize_append_path_element(name, name_ent.string_value()); + if (name.empty()) + { + if (m_info_hash.has_v1()) + name = aux::to_hex(m_info_hash.v1); + else + name = aux::to_hex(m_info_hash.v2); + } + + // extract file list + + // save a copy so that we can extract both v1 and v2 files then compare the results + file_storage v1_files; + if (version >= 2) + v1_files = files; + + bdecode_node const files_node = info.dict_find_list("files"); + + bdecode_node file_tree_node = info.dict_find_dict("file tree"); + if (version >= 2 && file_tree_node) + { + if (!extract_files2(file_tree_node, files, name, info_offset + , m_info_section.get(), bool(files_node), 0, ec)) + { + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + + files.sanitize_symlinks(); + if (files.num_files() > 1) + m_flags |= multifile; + else + m_flags &= ~multifile; + } + else if (version >= 2) + { + // mark the torrent as invalid + m_files.set_piece_length(0); + ec = errors::torrent_missing_file_tree; + return false; + } + else if (file_tree_node) + { + // mark the torrent as invalid + m_files.set_piece_length(0); + ec = errors::torrent_missing_meta_version; + return false; + } + + if (!files_node) + { + // if this is a v2 torrent it is ok for the length key to be missing + // that means it is a v2 only torrent + if (version < 2 || info.dict_find("length")) + { + // if there's no list of files, there has to be a length + // field. + if (!extract_single_file(info, version == 2 ? v1_files : files, "" + , info_offset, m_info_section.get(), true, ec)) + { + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + + m_flags &= ~multifile; + } + else + { + // this is a v2 only torrent so clear the v1 info hash to make sure no one uses it + m_info_hash.v1.clear(); + } + } + else + { + if (!extract_files(files_node, version == 2 ? v1_files : files, name + , info_offset, m_info_section.get(), 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; + } + + // ensure hybrid torrents have compatible v1 and v2 file storages + if (version >= 2 && v1_files.num_files() > 0) + { + // previous versions of libtorrent did not not create hybrid + // torrents with "tail-padding". When loading, accept both. + if (files.num_files() == v1_files.num_files() + 1) + { + files.remove_tail_padding(); + } + + if (!aux::files_compatible(files, v1_files)) + { + // mark the torrent as invalid + m_files.set_piece_length(0); + ec = errors::torrent_inconsistent_files; + 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() / files.piece_length() >= + std::numeric_limits::max()) + { + 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())); + + // 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; + } + + bdecode_node const pieces = info.dict_find_string("pieces"); + if (!pieces) + { + if (version < 2) + { + ec = errors::torrent_missing_pieces; + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + } + else + { + 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; + } + + std::ptrdiff_t const hash_offset = pieces.string_offset() - info_offset; + TORRENT_ASSERT(hash_offset < std::numeric_limits::max()); + TORRENT_ASSERT(hash_offset >= 0); + m_piece_hashes = static_cast(hash_offset); + TORRENT_ASSERT(m_piece_hashes > 0); + TORRENT_ASSERT(m_piece_hashes < m_info_section_size); + } + + m_flags |= (info.dict_find_int_value("private", 0) != 0) + ? private_torrent : torrent_info_flags_t{}; + +#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(static_cast( + similar.list_at(i).string_offset() - info_offset)); + } + } + + 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(std::int32_t(str.string_offset() - info_offset) + , str.string_length()); + } + } +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + + if (info.dict_find_string("ssl-cert")) + m_flags |= ssl_torrent; + + if (files.total_size() == 0) + { + ec = errors::torrent_invalid_length; + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + + // now, commit the files structure we just parsed out + // into the torrent_info object. + m_files.swap(files); + + TORRENT_ASSERT(m_info_hash.has_v2() == m_files.v2()); + return true; + } + + bool torrent_info::parse_piece_layers(bdecode_node const& e, error_code& ec) + { + std::map piece_layers; + + if (e.type() != bdecode_node::dict_t) + { + ec = errors::torrent_missing_piece_layer; + return false; + } + + for (int i = 0; i < e.dict_size(); ++i) + { + auto const f = e.dict_at(i); + if (f.first.size() != static_cast(sha256_hash::size()) + || f.second.type() != bdecode_node::string_t + || f.second.string_length() % sha256_hash::size() != 0) + { + ec = errors::torrent_invalid_piece_layer; + return false; + } + + piece_layers.emplace(sha256_hash(f.first), f.second.string_value()); + } + + m_piece_layers.resize(orig_files().num_files()); + + for (file_index_t i : orig_files().file_range()) + { + if (orig_files().file_size(i) <= orig_files().piece_length()) + continue; + + auto const piece_layer = piece_layers.find(orig_files().root(i)); + if (piece_layer == piece_layers.end()) continue; + + int const num_pieces = orig_files().file_num_pieces(i); + + if (ptrdiff_t(piece_layer->second.size()) != num_pieces * sha256_hash::size()) + { + ec = errors::torrent_invalid_piece_layer; + return false; + } + + auto const hashes = piece_layer->second; + if ((hashes.size() % sha256_hash::size()) != 0) + { + ec = errors::torrent_invalid_piece_layer; + return false; + } + + m_piece_layers[i].assign(hashes.begin(), hashes.end()); + } + + m_flags |= v2_has_piece_hashes; + return true; + } + + span torrent_info::piece_layer(file_index_t f) const + { + TORRENT_ASSERT_PRECOND(f >= file_index_t(0)); + if (f >= m_piece_layers.end_index()) return {}; + if (m_files.pad_file_at(f)) return {}; + + if (m_files.file_size(f) <= piece_length()) + { + auto const root_ptr = m_files.root_ptr(f); + if (root_ptr == nullptr) return {}; + return {root_ptr, lt::sha256_hash::size()}; + } + return m_piece_layers[f]; + } + + void torrent_info::free_piece_layers() + { + m_piece_layers.clear(); + m_piece_layers.shrink_to_fit(); + + m_flags &= ~v2_has_piece_hashes; + } + + 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); } + + 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::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_hashes; + 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(); + + if (m_info_hash.has_v2()) + { + // allow torrent files without piece layers, just like we allow magnet + // links. However, if there are piece layers, make sure they're + // valid + bdecode_node const& e = torrent_file.dict_find_dict("piece layers"); + if (e && !parse_piece_layers(e, ec)) + { + TORRENT_ASSERT(ec); + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + } + +#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()); + ltrim(e.url); + 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; + ltrim(e.url); +#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(std::move(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(std::move(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 u = maybe_url_encode(url.string_value().to_string()); + if (!unique.insert(u).second) continue; + m_web_seeds.emplace_back(std::move(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(); + aux::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(); + aux::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; }); + } + + void torrent_info::clear_trackers() + { + m_urls.clear(); + } + +#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; + } +#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(m_info_section.get() + 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(m_info_section.get() + 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(i).data() != nullptr); + if (!m_files.owns_name(i)) + { + // name needs to point into the allocated info section buffer + TORRENT_ASSERT(m_files.file_name(i).data() >= m_info_section.get()); + TORRENT_ASSERT(m_files.file_name(i).data() < m_info_section.get() + m_info_section_size); + } + else + { + // name must be a null terminated string + string_view const name = m_files.file_name(i); + TORRENT_ASSERT(name.data()[name.size()] == '\0'); + } + } + + TORRENT_ASSERT(m_piece_hashes <= m_info_section_size); + } +#endif + + sha1_hash torrent_info::info_hash() const noexcept + { + return m_info_hash.get_best(); + } + + bool torrent_info::v1() const { return m_info_hash.has_v1(); } + bool torrent_info::v2() const { return m_info_hash.has_v2(); } + +TORRENT_VERSION_NAMESPACE_3_END + +} diff --git a/src/torrent_peer.cpp b/src/torrent_peer.cpp new file mode 100644 index 0000000..429971c --- /dev/null +++ b/src/torrent_peer.cpp @@ -0,0 +1,286 @@ +/* + +Copyright (c) 2014-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2018, 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/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 +#include "libtorrent/aux_/ip_helpers.hpp" + +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(aux::is_v4(e1) == aux::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); + aux::write_uint16(e1.port(), ptr); + aux::write_uint16(e2.port(), ptr); + ret = crc32c_32(p); + } + else if (aux::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) + , maybe_upload_only(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) + , protocol_v2(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 + return address().to_string(); + } +#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 {}; + 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..7c84888 --- /dev/null +++ b/src/torrent_peer_allocator.cpp @@ -0,0 +1,117 @@ +/* + +Copyright (c) 2014-2017, 2019, Arvid Norberg +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/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..780f261 --- /dev/null +++ b/src/torrent_status.cpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2016-2019, Arvid Norberg +Copyright (c) 2016-2017, 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/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; + + 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..43cef94 --- /dev/null +++ b/src/tracker_manager.cpp @@ -0,0 +1,501 @@ +/* + +Copyright (c) 2004-2010, 2012-2020, Arvid Norberg +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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" +#include "libtorrent/ssl.hpp" + +using namespace std::placeholders; + +namespace libtorrent { + +constexpr tracker_request_flags_t tracker_request::scrape_request; +constexpr tracker_request_flags_t tracker_request::i2p; + + timeout_handler::timeout_handler(io_context& 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"); + m_timeout.expires_at(m_read_time + seconds(timeout)); + 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; + m_timeout.cancel(); + } + + 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"); + m_timeout.expires_at(m_read_time + seconds(timeout)); + 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 req + , io_context& ios + , std::weak_ptr r) + : timeout_handler(ios) + , m_req(std::move(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, operation_t const op + , char const* msg, seconds32 const interval, seconds32 const min_interval) + { + // we need to post the error to avoid deadlock + post(get_executor(), std::bind(&tracker_connection::fail_impl + , shared_from_this(), ec, op, std::string(msg), interval, min_interval)); + } + + void tracker_connection::fail_impl(error_code const& ec, operation_t const op + , 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, op, 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 send_fun + , send_fun_hostname_t send_fun_hostname + , counters& stats_counters + , aux::resolver_interface& resolver + , aux::session_settings const& sett +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + , aux::session_logger& ses +#endif + ) + : m_send_fun(std::move(send_fun)) + , m_send_fun_hostname(std::move(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_context& 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 == event_t::stopped); + if (m_abort && req.event != event_t::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(':')); + +#if TORRENT_USE_SSL + 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()) + post(ios, std::bind(&request_callback::tracker_request_error, r, std::move(req) + , errors::unsupported_url_protocol, operation_t::parse_address + , "", 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::stop() + { + abort_all_requests(); + m_abort = true; + } + + 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 + + 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 == event_t::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 == event_t::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 == event_t::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/truncate.cpp b/src/truncate.cpp new file mode 100644 index 0000000..fe23e86 --- /dev/null +++ b/src/truncate.cpp @@ -0,0 +1,177 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/truncate.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/operations.hpp" + +#ifdef TORRENT_WINDOWS +#include "libtorrent/aux_/windows.hpp" +#else +#include +#endif + +namespace libtorrent { + +#ifdef TORRENT_WINDOWS + +void truncate_files(file_storage const& fs, std::string const& save_path, storage_error& ec) +{ + for (auto i : fs.file_range()) + { + if (fs.pad_file_at(i)) continue; + auto const fn = fs.file_path(i, save_path); + native_path_string const file_path = convert_to_native_path_string(fn); +#ifdef TORRENT_WINRT + HANDLE handle = CreateFile2(file_path.c_str() + , GENERIC_WRITE | GENERIC_READ + , FILE_SHARE_READ | FILE_SHARE_WRITE + , OPEN_EXISTING + , nullptr); +#else + HANDLE handle = CreateFileW(file_path.c_str() + , GENERIC_WRITE | GENERIC_READ + , FILE_SHARE_READ | FILE_SHARE_WRITE + , nullptr + , OPEN_EXISTING + , 0 + , nullptr); +#endif + if (handle == INVALID_HANDLE_VALUE) + { + auto const error = ::GetLastError(); + if (error != ERROR_FILE_NOT_FOUND) + { + ec.ec.assign(error, system_category()); + ec.file(i); + ec.operation = operation_t::file_open; + return; + } + continue; + } + + LARGE_INTEGER file_size; + if (GetFileSizeEx(handle, &file_size) == FALSE) + { + ec.ec.assign(::GetLastError(), system_category()); + ec.file(i); + ec.operation = operation_t::file_stat; + ::CloseHandle(handle); + return; + } + + if (file_size.QuadPart < fs.file_size(i)) + { + ::CloseHandle(handle); + continue; + } + + LARGE_INTEGER sz; + sz.QuadPart = fs.file_size(i); + if (SetFilePointerEx(handle, sz, nullptr, FILE_BEGIN) == FALSE) + { + ec.ec.assign(::GetLastError(), system_category()); + ec.file(i); + ec.operation = operation_t::file_seek; + ::CloseHandle(handle); + return; + } + + if (::SetEndOfFile(handle) == FALSE) + { + ec.ec.assign(::GetLastError(), system_category()); + ec.file(i); + ec.operation = operation_t::file_truncate; + ::CloseHandle(handle); + return; + } + ::CloseHandle(handle); + } +} + +#else + +void truncate_files(file_storage const& fs, std::string const& save_path, storage_error& ec) +{ + for (auto i : fs.file_range()) + { + if (fs.pad_file_at(i)) continue; + auto const fn = fs.file_path(i, save_path); + native_path_string const file_path = convert_to_native_path_string(fn); + int const fd = ::open(file_path.c_str(), O_RDWR); + + if (fd < 0) + { + int const error = errno; + if (error != ENOENT) + { + ec.ec.assign(error, generic_category()); + ec.file(i); + ec.operation = operation_t::file_open; + return; + } + continue; + } + + struct ::stat st; + if (::fstat(fd, &st) != 0) + { + ec.ec.assign(errno, system_category()); + ec.file(i); + ec.operation = operation_t::file_stat; + ::close(fd); + return; + } + + if (st.st_size < fs.file_size(i)) + { + ::close(fd); + continue; + } + + if (::ftruncate(fd, static_cast(fs.file_size(i))) < 0) + { + ec.ec.assign(errno, system_category()); + ec.file(i); + ec.operation = operation_t::file_truncate; + ::close(fd); + return; + } + + ::close(fd); + } +} + +#endif + +} diff --git a/src/udp_socket.cpp b/src/udp_socket.cpp new file mode 100644 index 0000000..d1069b8 --- /dev/null +++ b/src/udp_socket.cpp @@ -0,0 +1,1000 @@ +/* + +Copyright (c) 2007-2020, Arvid Norberg +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016-2018, 2020, 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/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/aux_/ip_helpers.hpp" // for is_v4 +#include "libtorrent/aux_/alert_manager.hpp" +#include "libtorrent/socks5_stream.hpp" // for socks_error +#include "libtorrent/aux_/keepalive.hpp" +#include "libtorrent/aux_/resolver_interface.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_context& ios, aux::listen_socket_handle ls + , aux::alert_manager& alerts, aux::resolver_interface& res, bool const send_local_ep) + : m_socks5_sock(ios) + , m_resolver(res) + , m_timer(ios) + , m_retry_timer(ios) + , m_alerts(alerts) + , m_listen_socket(std::move(ls)) + , m_send_local_ep(send_local_ep) + {} + + 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, std::vector
    const& ips); + 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; + aux::resolver_interface& m_resolver; + deadline_timer m_timer; + deadline_timer m_retry_timer; + aux::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; + + // include our local IP and listen port in the UDP associate command + // Doing so may be risky in case we're talking to the proxy via NAT, and we + // don't actually know our IP from the proxy's point of view + bool m_send_local_ep = false; + + // 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_context& ios, aux::listen_socket_handle ls) + : m_socket(ios) + , m_ioc(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) + && aux::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::aux; + + std::array header; + char* h = header.data(); + + write_uint16(0, h); // reserved + write_uint8(0, h); // fragment + write_uint8(aux::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) && aux::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::aux; + + 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) + && aux::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::aux; + + // 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 + , aux::alert_manager& alerts, aux::resolver_interface& resolver, bool const send_local_ep) +{ + 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(m_ioc + , m_listen_socket, alerts, resolver, send_local_ep); + m_socks5_connection->start(ps); + } +} + +// ===================== SOCKS 5 ========================= + +void socks5::start(aux::proxy_settings const& ps) +{ + m_proxy_settings = ps; + + ADD_OUTSTANDING_ASYNC("socks5::on_name_lookup"); + m_proxy_addr.port(ps.port); + m_resolver.async_resolve(ps.hostname, aux::resolver_interface::abort_on_shutdown + , std::bind(&socks5::on_name_lookup, self(), _1, _2)); +} + +void socks5::on_name_lookup(error_code const& e, std::vector
    const& ips) +{ + 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. + auto const i = std::find_if(ips.begin(), ips.end() + , [&](address const& a) { + return m_listen_socket.can_route(a); + }); + + if (i == ips.end()) + { + 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; + } + + m_proxy_addr.address(*i); + + error_code ec; + m_socks5_sock.open(aux::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 keep-alives + 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_after(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::aux; + + // 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::aux; + + 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::aux; + + 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::aux; + + // 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 + + if (m_send_local_ep) + { + auto const local_ep = m_listen_socket.get_local_endpoint(); + write_uint8(aux::is_v4(local_ep) ? 1 : 4, p); // atyp + write_endpoint(local_ep, p); + } + else + { + 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::aux; + + 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_after(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_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..28ce536 --- /dev/null +++ b/src/udp_tracker_connection.cpp @@ -0,0 +1,780 @@ +/* + +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2004-2020, Arvid Norberg +Copyright (c) 2016-2017, Steven Siloti +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2016-2018, 2020, 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 "libtorrent/parse_url.hpp" +#include "libtorrent/udp_tracker_connection.hpp" +#include "libtorrent/hex.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/aux_/resolver_interface.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/aux_/io.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" // for is_v6 +#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_context& 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, operation_t::parse_address); + 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 == event_t::stopped + ? aux::resolver_interface::cache_only : aux::resolver_flags{}) + | aux::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 == event_t::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, operation_t const op + , 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, op, 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 + post(get_executor(), std::bind( + &udp_tracker_connection::start_announce, shared_from_this())); + + aux::session_settings const& settings = m_man.settings(); + set_timeout(tracker_req().event == event_t::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, operation_t::hostname_lookup); + 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), operation_t::hostname_lookup); + return; + } + + restart_read_timeout(); + + if (!tracker_req().outgoing_socket) + { + fail(error_code(errors::invalid_listen_socket), operation_t::hostname_lookup); + 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(lt::errors::announce_skipped, operation_t::hostname_lookup); + 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), operation_t::hostname_lookup); + 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, operation_t::timer); + 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), operation_t::timer); + } + + 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 (!m_target.address().is_unspecified() && 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), operation_t::bittorrent + , 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 (!(tracker_req().kind & tracker_request::scrape_request)) + send_udp_announce(); + else if (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, operation_t::sock_write); + 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, operation_t::sock_write); + 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 = aux::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), operation_t::bittorrent); + 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 (aux::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), operation_t::bittorrent); + return false; + } + + if (action == action_t::error) + { + fail(error_code(errors::tracker_failure), operation_t::bittorrent + , std::string(buf.data(), static_cast(buf.size())).c_str()); + return true; + } + + if (action != action_t::scrape) + { + fail(error_code(errors::invalid_tracker_action), operation_t::bittorrent); + return true; + } + + if (buf.size() < 12) + { + fail(error_code(errors::invalid_tracker_response_length), operation_t::bittorrent); + 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_uint(), 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, operation_t::sock_write); + return; + } + } + +} diff --git a/src/upnp.cpp b/src/upnp.cpp new file mode 100644 index 0000000..90094e6 --- /dev/null +++ b/src/upnp.cpp @@ -0,0 +1,1676 @@ +/* + +Copyright (c) 2019, Amir Abrams +Copyright (c) 2007-2020, Arvid Norberg +Copyright (c) 2009, Andrew Resch +Copyright (c) 2015, Mike Tzou +Copyright (c) 2016-2019, Alden Torres +Copyright (c) 2016-2017, Pavel Pimenov +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016-2017, Andrei Kurushin +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/ssl.hpp" + +#if defined TORRENT_ASIO_DEBUGGING +#include "libtorrent/debug.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#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&&) noexcept = default; +upnp::rootdevice& upnp::rootdevice::operator=(rootdevice&&) & = default; + +// TODO: 2 use boost::asio::ip::network instead of netmask +upnp::upnp(io_context& ios + , aux::session_settings const& settings + , aux::portmap_callback& cb + , address_v4 const listen_address + , address_v4 const netmask + , std::string listen_device + , listen_socket_handle ls) + : 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)) +#if TORRENT_USE_SSL + , m_ssl_ctx(ssl::context::sslv23_client) +#endif + , m_listen_handle(std::move(ls)) +{ +#if TORRENT_USE_SSL + 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, m_listen_handle); +} +#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"; + +#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_after(seconds(2 * m_retry_count)); + 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) + { + auto& 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) + { + auto& 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() + , hostname_filter_handler() +#if TORRENT_USE_SSL + , &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_after(seconds(1)); + 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() + , hostname_filter_handler() +#if TORRENT_USE_SSL + , &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() + , hostname_filter_handler() +#if TORRENT_USE_SSL + , &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; + // default to the first (or only) control url in the router's listing + if (!state.in_service + && state.top_tags("service", "servicetype") + && state.service_type.empty()) + { + 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() + , hostname_filter_handler() +#if TORRENT_USE_SSL + , &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, m_listen_handle); + } + + // we cannot clear the devices since there + // might be outstanding requests relying on + // the device entry being present when they + // complete + m_broadcast_timer.cancel(); + m_refresh_timer.cancel(); + m_map_timer.cancel(); + error_code 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, m_listen_handle); + 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.expiry(); + if (next_expire < now || next_expire > m.expires) + { + ADD_OUTSTANDING_ASYNC("upnp::on_expire"); + m_refresh_timer.expires_at(m.expires); + 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, m_listen_handle); +} + +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, m_listen_handle); + + 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) + { + auto& 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"); + m_refresh_timer.expires_at(next_expire); + m_refresh_timer.async_wait(std::bind(&upnp::on_expire, self(), _1)); + } +} + +void upnp::close() +{ + TORRENT_ASSERT(is_single_thread()); + + m_refresh_timer.cancel(); + m_broadcast_timer.cancel(); + m_map_timer.cancel(); + m_closing = true; + error_code ec; + m_unicast_socket.close(ec); + m_multicast_socket.close(ec); + + for (auto& dev : m_devices) + { + auto& 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..06881b2 --- /dev/null +++ b/src/ut_metadata.cpp @@ -0,0 +1,635 @@ +/* + +Copyright (c) 2009, Andrew Resch +Copyright (c) 2007-2020, Arvid Norberg +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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, + }; + + enum class msg_t : std::uint8_t + { + request, piece, dont_have + }; + + 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) {} + + std::shared_ptr new_connection( + peer_connection_handle const& pc) override; + + span metadata() const + { + if (!m_metadata.empty()) return m_metadata; + if (!m_torrent.valid_metadata()) return {}; + + auto const ret = m_torrent.torrent_file().info_section(); + +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + if (m_torrent.torrent_file().info_hashes().has_v1()) + { + TORRENT_ASSERT(hasher(ret).final() + == m_torrent.torrent_file().info_hashes().v1); + } + if (m_torrent.torrent_file().info_hashes().has_v2()) + { + TORRENT_ASSERT(hasher256(ret).final() + == m_torrent.torrent_file().info_hashes().v2); + } +#endif + + return ret; + } + + 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_torrent.valid_metadata()) return; + if (size <= 0 || size > 4 * 1024 * 1024) return; + m_metadata.resize(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. Once we have metadata, we seed it directly from the + // torrent_info of the underlying torrent + aux::vector m_metadata; + + struct metadata_piece + { + metadata_piece() = default; + int num_requests = 0; + time_point last_request = min_time(); + 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.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(msg_t const type, int const piece) + { + TORRENT_ASSERT(!m_pc.associated_torrent().expired()); + +#ifndef TORRENT_DISABLE_LOGGING + static char const* names[] = {"request", "data", "dont-have"}; + char const* n = ""; + if (type >= msg_t::request && type <= msg_t::dont_have) n = names[static_cast(type)]; + m_pc.peer_log(peer_log_alert::outgoing_message, "UT_METADATA" + , "type: %d (%s) piece: %d", static_cast(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"] = static_cast(type); + e["piece"] = piece; + + char const* metadata = nullptr; + int metadata_piece_size = 0; + + if (m_torrent.valid_metadata()) + e["total_size"] = m_tp.metadata().size(); + + if (type == msg_t::piece) + { + TORRENT_ASSERT(piece >= 0 && piece < (m_tp.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( + int(m_tp.metadata().size()) - offset, 16 * 1024); + TORRENT_ASSERT(metadata_piece_size > 0); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(offset + metadata_piece_size <= m_tp.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 = aux; + 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; + } + auto const type = msg_t(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", static_cast(type), piece); +#endif + + switch (type) + { + case msg_t::request: + { + if (!m_torrent.valid_metadata() + || piece < 0 || piece >= (m_tp.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() + ? int(m_tp.metadata().size()) : 0); + } +#endif + write_metadata_packet(msg_t::dont_have, piece); + return true; + } + if (m_pc.send_buffer_size() < send_buffer_limit) + write_metadata_packet(msg_t::piece, piece); + else if (m_incoming_requests.size() < max_incoming_requests) + m_incoming_requests.push_back(piece); + else + write_metadata_packet(msg_t::dont_have, piece); + } + break; + case msg_t::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 msg_t::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; + } + + 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(msg_t::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(msg_t::request, 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 info hash 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.empty()) + { + // 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.resize(total_size); + m_requested_metadata.resize(div_round_up(total_size, 16 * 1024)); + } + + 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.end_index()) + { +#ifndef TORRENT_DISABLE_LOGGING + source.m_pc.peer_log(peer_log_alert::info, "UT_METADATA" + , "total_size: %d INCONSISTENT WITH: %d" + , total_size, int(metadata().size())); +#endif + // they disagree about the size! + return false; + } + + if (piece * 16 * 1024 + buf.size() > 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)) + { + 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.clear(); + m_metadata.shrink_to_fit(); + + // 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, client_data_t) + { + 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..8fcdef8 --- /dev/null +++ b/src/ut_pex.cpp @@ -0,0 +1,644 @@ +/* + +Copyright (c) 2006, MassaRoddel +Copyright (c) 2006-2020, Arvid Norberg +Copyright (c) 2015, 2018, Steven Siloti +Copyright (c) 2016-2017, Andrei Kurushin +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/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" +#include "libtorrent/aux_/ip_helpers.hpp" // for is_v4 + +#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; + + auto const* const 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 (aux::is_v4(remote)) + { + aux::write_endpoint(remote, pla_out); + aux::write_uint8(static_cast(flags), plf_out); + } + else + { + aux::write_endpoint(remote, pla6_out); + aux::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 (aux::is_v4(i)) + aux::write_endpoint(i, pld_out); + else + aux::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 = aux::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 = aux::read_v4_endpoint(in); + pex_flags_t flags(static_cast(*fin++)); + + // this is an internal flag. disregard it from the internet + flags &= ~pex_lt_v2; + + 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 (aux::is_local(adr.address()) && !aux::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 = aux::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 = aux::read_v6_endpoint(in); + pex_flags_t flags(static_cast(*fin++)); + + // this is an internal flag. disregard it from the internet + flags &= ~pex_lt_v2; + + // ignore local addresses unless the peer is local to us + if (aux::is_local(adr.address()) && !aux::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; + } + int const num_peers = m_torrent.num_peers(); + if (num_peers <= 1) return; + + 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; + + aux::write_uint32(1 + 1 + int(pex_msg.size()), ptr); + aux::write_uint8(bt_peer_connection::msg_extended, ptr); + aux::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; + + auto const* const 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 (aux::is_v4(remote)) + { + aux::write_endpoint(remote, pla_out); + aux::write_uint8(flags, plf_out); + } + else + { + aux::write_endpoint(remote, pla6_out); + aux::write_uint8(flags, plf6_out); + } + ++num_added; + } + std::vector pex_msg; + bencode(std::back_inserter(pex_msg), pex); + + char msg[6]; + char* ptr = msg; + + aux::write_uint32(1 + 1 + int(pex_msg.size()), ptr); + aux::write_uint8(bt_peer_connection::msg_extended, ptr); + aux::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, client_data_t) + { + 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..6242ee0 --- /dev/null +++ b/src/utf8.cpp @@ -0,0 +1,177 @@ +/* + +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2012, 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2017, 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 +#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 < 0b10000000) return 1; + if ((b >> 5) == 0b110) return 2; + if ((b >> 4) == 0b1110) return 3; + if ((b >> 3) == 0b11110) return 4; + // this is an invalid prefix, but we still parse it to skip this many + // bytes + if ((b >> 2) == 0b111110) return 5; + return 0; + } + +} // anonymous namespace + + std::int32_t const max_codepoint = 0x10ffff; + std::int32_t const surrogate_start = 0xd800; + std::int32_t const surrogate_end = 0xdfff; + + void append_utf8_codepoint(std::string& ret, std::int32_t codepoint) + { + 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(0b11000000 | (codepoint >> 6))); + break; + case 3: + ret.push_back(static_cast(0b11100000 | (codepoint >> 12))); + break; + case 4: + ret.push_back(static_cast(0b11110000 | (codepoint >> 18))); + break; + } + + for (int i = seq_len - 2; i >= 0; --i) + ret.push_back(static_cast(0b10000000 | ((codepoint >> (6 * i)) & 0b111111))); + } + + // 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] & 0b01111111; + break; + case 2: + ch = str[0] & 0b00011111; + break; + case 3: + ch = str[0] & 0b00001111; + break; + case 4: + ch = str[0] & 0b00000111; + 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 > 0b10111111 || b < 0b10000000) + return std::make_pair(-1, sequence_len); + ch <<= 6; + ch += b & 0b111111; + } + + // 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); + + // per RFC 3629, surrogates should not appear in utf-8 + if (ch >= surrogate_start && ch <= surrogate_end) + 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..5c389ad --- /dev/null +++ b/src/utp_socket_manager.cpp @@ -0,0 +1,332 @@ +/* + +Copyright (c) 2010-2020, Arvid Norberg +Copyright (c) 2016-2019, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_/utp_stream.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/aux_/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/aux_/ip_helpers.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 { +namespace aux { + + utp_socket_manager::utp_socket_manager( + send_fun_t send_fun + , incoming_utp_callback_t cb + , io_context& ios + , aux::session_settings const& sett + , counters& cnt + , void* ssl_context) + : m_send_fun(std::move(send_fun)) + , m_cb(std::move(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() = default; + + void utp_socket_manager::tick(time_point now) + { + for (auto i = m_utp_sockets.begin() + , end(m_utp_sockets.end()); i != end;) + { + if (i->second->should_delete()) + { + if (m_last_socket == i->second.get()) m_last_socket = nullptr; + if (m_deferred_ack == i->second.get()) m_deferred_ack = nullptr; + i = m_utp_sockets.erase(i); + continue; + } + i->second->tick(now); + ++i; + } + } + + int utp_socket_manager::mtu_for_dest(address const& addr) const + { + int mtu = 0; + if (aux::is_teredo(addr)) mtu = TORRENT_TEREDO_MTU; + else mtu = TORRENT_ETHERNET_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::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 const 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 && m_last_socket->match(ep, id)) + { + return m_last_socket->incoming_packet(p, ep, receive_time); + } + + if (m_deferred_ack) + { + m_deferred_ack->send_ack(); + m_deferred_ack = nullptr; + } + + auto r = m_utp_sockets.equal_range(id); + + for (; r.first != r.second; ++r.first) + { + if (!r.first->second->match(ep, id)) continue; + bool const ret = r.first->second->incoming_packet(p, ep, receive_time); + if (ret) m_last_socket = r.first->second.get(); + 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; + + TORRENT_ASSERT(m_new_connection == -1); + // create the new socket with this ID + m_new_connection = id; + +// UTP_LOGV("not found, new connection id:%d\n", m_new_connection); + + // TODO: this should not be heap allocated, sockets should be movable + aux::socket_type c(aux::instantiate_connection(m_ios, aux::proxy_settings(), m_ssl_context, this, true, false)); + + utp_stream* str = nullptr; +#ifdef TORRENT_SSL_PEERS + if (is_ssl(c)) + str = &boost::get>(c).next_layer(); + else +#endif + str = boost::get(&c); + + TORRENT_ASSERT(str); + int const mtu = mtu_for_dest(ep.address()); + str->get_impl()->init_mtu(mtu); + str->get_impl()->m_sock = std::move(socket); + bool const ret = str->get_impl()->incoming_packet(p, ep, receive_time); + if (!ret) return false; + m_last_socket = str->get_impl(); + m_cb(std::move(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) + { + s->writable(); + } + } + } + + void utp_socket_manager::socket_drained() + { + if (m_deferred_ack) + { + utp_socket_impl* s = m_deferred_ack; + m_deferred_ack = nullptr; + s->send_ack(); + } + + if (!m_drained_event.empty()) + { + m_temp_sockets.clear(); + m_drained_event.swap(m_temp_sockets); + for (auto const &s : m_temp_sockets) + s->socket_drained(); + } + } + + void utp_socket_manager::defer_ack(utp_socket_impl* s) + { + TORRENT_ASSERT(m_deferred_ack == nullptr || 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) + { + auto iface = sock.lock(); + for (auto& s : m_utp_sockets) + { + if (s.second->m_sock.lock() != iface) + continue; + + s.second->abort(); + } + } + + 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; + if (m_last_socket == i->second.get()) m_last_socket = nullptr; + if (m_deferred_ack == i->second.get()) 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; + } + auto impl = std::make_unique(recv_id, send_id, str, *this); + auto const ret = impl.get(); + m_utp_sockets.emplace(recv_id, std::move(impl)); + return ret; + } +} +} diff --git a/src/utp_stream.cpp b/src/utp_stream.cpp new file mode 100644 index 0000000..6c61299 --- /dev/null +++ b/src/utp_stream.cpp @@ -0,0 +1,3343 @@ +/* + +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2010-2020, Arvid Norberg +Copyright (c) 2015-2018, Alden Torres +Copyright (c) 2016, milesdong +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2018, V.G. Bulavintsev +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_/utp_stream.hpp" +#include "libtorrent/aux_/utp_socket_manager.hpp" +#include "libtorrent/aux_/alloca.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t +#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 { +namespace aux { + +#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; +} + +utp_socket_impl::utp_socket_impl(std::uint16_t const recv_id + , std::uint16_t const send_id + , utp_stream* 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(static_cast(state_t::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()); +} + +tcp::endpoint utp_socket_impl::remote_endpoint(error_code& ec) const +{ + if (state() == state_t::none) + ec = boost::asio::error::not_connected; + else + TORRENT_ASSERT(m_remote_address != address_v4::any()); + return {m_remote_address, m_port}; +} + +packet_ptr utp_socket_impl::acquire_packet(int const allocate) +{ + return m_sm.acquire_packet(allocate); +} + +void utp_socket_impl::release_packet(packet_ptr p) +{ + m_sm.release_packet(std::move(p)); +} + +void utp_socket_impl::abort() +{ + m_error = boost::asio::error::connection_aborted; + set_state(utp_socket_impl::state_t::error_wait); + test_socket_state(); +} + +bool utp_socket_impl::match(udp::endpoint const& ep, std::uint16_t const id) const +{ + return m_recv_id == id + && m_port == ep.port() + && m_remote_address == ep.address(); +} + +udp::endpoint utp_socket_impl::remote_endpoint() const +{ + return {m_remote_address, m_port}; +} + +void utp_socket_impl::send_ack() +{ + TORRENT_ASSERT(m_deferred_ack); + m_deferred_ack = false; + send_pkt(utp_socket_impl::pkt_ack); +} + +void utp_socket_impl::socket_drained() +{ + 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 + maybe_trigger_receive_callback(); + maybe_trigger_send_callback(); +} + +void utp_socket_impl::update_mtu_limits() +{ + INVARIANT_CHECK; + + if (m_mtu_floor > m_mtu_ceiling) + { + // this is the case where we drop an MTU probe once we're in steady + // state. Assume the probe was lost by chance, and don't decrement the + // ceiling. We're still restarting the Path MTU discovery, so if the MTU + // did in fact chance, we'll be notified again, when not in steady + // state. + m_mtu_ceiling = m_mtu_floor; + + // the path MTU may have changed. Perform another search + // dont' start all the way from start, just half way down. + m_mtu_floor = ((TORRENT_INET_MIN_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER) + m_mtu_ceiling) / 2; + + UTP_LOGV("%8p: reducing MTU floor\n", static_cast(this)); + } + + 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_stream::send_delay() const +{ + return m_impl ? m_impl->send_delay() : 0; +} + +int utp_stream::recv_delay() const +{ + return m_impl ? m_impl->recv_delay() : 0; +} + +utp_stream::utp_stream(io_context& io_context) + : m_io_service(io_context) + , 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() const +{ + return m_incoming_close_reason; +} + +void utp_stream::close() +{ + if (!m_impl) return; + if (!m_impl->destroy()) + { + if (!m_impl) return; + m_impl->detach(); + 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 {}; + } + 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 {}; + } + + auto s = m_impl->m_sock.lock(); + if (!s) + { + ec = boost::asio::error::not_connected; + return {}; + } + + udp::endpoint ep = s->get_local_endpoint(); + return {ep.address(), ep.port()}; +} + +utp_stream::utp_stream(utp_stream&& rhs) noexcept + : m_io_service(rhs.m_io_service) + , m_impl(rhs.m_impl) + , m_open(rhs.m_open) +{ + if (&rhs == this) return; + rhs.m_open = false; + rhs.m_impl = nullptr; + if (m_impl) m_impl->set_userdata(this); +} + +utp_stream::~utp_stream() +{ + if (m_impl) + { + UTP_LOGV("%8p: utp_stream destructed\n", static_cast(m_impl)); + m_impl->destroy(); + m_impl->detach(); + 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->receive_buffer_size(); +} + +void utp_stream::on_close_reason(utp_stream* s, close_reason_t reason) +{ + // 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(utp_stream* s, std::size_t const bytes_transferred + , error_code const& ec, bool const shutdown) +{ + 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->null_buffers()); + post(s->m_io_service, std::bind(std::move(s->m_read_handler), ec, bytes_transferred)); + s->m_read_handler = nullptr; + if (shutdown && s->m_impl) + { + TORRENT_ASSERT(ec); + s->m_impl->detach(); + s->m_impl = nullptr; + } +} + +void utp_stream::on_write(utp_stream* s, std::size_t const bytes_transferred + , error_code const& ec, bool const shutdown) +{ + 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); + post(s->m_io_service, std::bind(std::move(s->m_write_handler), ec, bytes_transferred)); + s->m_write_handler = nullptr; + if (shutdown && s->m_impl) + { + TORRENT_ASSERT(ec); + s->m_impl->detach(); + s->m_impl = nullptr; + } +} + +void utp_stream::on_connect(utp_stream* s, error_code const& ec, bool const shutdown) +{ + 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); + post(s->m_io_service, std::bind(std::move(s->m_connect_handler), ec)); + s->m_connect_handler = nullptr; + if (shutdown && s->m_impl) + { + TORRENT_ASSERT(ec); + s->m_impl->detach(); + s->m_impl = nullptr; + } +} + +void utp_stream::add_read_buffer(void* buf, int const len) +{ + TORRENT_ASSERT(m_impl); + TORRENT_ASSERT(len < INT_MAX); + TORRENT_ASSERT(len > 0); + TORRENT_ASSERT(buf); + m_impl->add_read_buffer(buf, 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, int const len) +{ + TORRENT_ASSERT(m_impl); + TORRENT_ASSERT(len < INT_MAX); + TORRENT_ASSERT(len > 0); + TORRENT_ASSERT(buf); + m_impl->add_write_buffer(buf, 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() +{ + m_impl->issue_read(); +} + +std::size_t utp_stream::read_some(bool const clear_buffers) +{ + return m_impl->read_some(clear_buffers); +} + +// Warning: this is always non-blocking, it only tries to send +// immediately if there is some space in the congestion window. +std::size_t utp_stream::write_some(bool const clear_buffers) +{ + return m_impl->write_some(clear_buffers); +} + +// 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() +{ + m_impl->issue_write(); +} + +void utp_stream::do_connect(tcp::endpoint const& ep) +{ + m_impl->do_connect(ep); +} + +// =========== utp_socket_impl ============ + +void utp_socket_impl::add_read_buffer(void* buf, int const len) +{ + UTP_LOGV("%8p: add_read_buffer %d bytes\n", static_cast(this), len); + if (len <= 0) return; + m_read_buffer.emplace_back(reinterpret_cast(buf), len); + m_read_buffer_size += len; +} + +void utp_socket_impl::add_write_buffer(void const* buf, int const len) +{ + UTP_LOGV("%8p: add_write_buffer %d bytes\n", static_cast(this), len); + if (len <= 0) return; + +#if TORRENT_USE_ASSERTS + std::ptrdiff_t write_buffer_size = 0; + for (auto const& i : m_write_buffer) + { + TORRENT_ASSERT(std::numeric_limits::max() - i.size() > write_buffer_size); + write_buffer_size += i.size(); + } + TORRENT_ASSERT(m_write_buffer_size == write_buffer_size); +#endif + + m_write_buffer.emplace_back(reinterpret_cast(buf), len); + m_write_buffer_size += len; + +#if TORRENT_USE_ASSERTS + write_buffer_size = 0; + for (auto const& i : m_write_buffer) + { + TORRENT_ASSERT(std::numeric_limits::max() - i.size() > write_buffer_size); + write_buffer_size += i.size(); + } + TORRENT_ASSERT(m_write_buffer_size == write_buffer_size); +#endif +} + +void utp_socket_impl::issue_read() +{ + TORRENT_ASSERT(m_userdata); + TORRENT_ASSERT(!m_read_handler); + + m_null_buffers = m_read_buffer_size == 0; + + m_read_handler = true; + if (test_socket_state()) return; + + UTP_LOGV("%8p: new read handler. %d bytes in buffer\n" + , static_cast(this), 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_read += int(read_some(false)); + maybe_trigger_receive_callback(); +} + +std::size_t utp_socket_impl::read_some(bool const clear_buffers) +{ + TORRENT_ASSERT(m_receive_buffer_size >= 0); + if (m_receive_buffer_size <= 0) + { + if (clear_buffers) + { + m_read_buffer_size = 0; + m_read_buffer.clear(); + } + return 0; + } + + auto target = m_read_buffer.begin(); + + std::size_t ret = 0; + + int pop_packets = 0; + for (auto i = m_receive_buffer.begin() + , end(m_receive_buffer.end()); i != end;) + { + if (target == m_read_buffer.end()) + { + UTP_LOGV(" No more target buffers: %d bytes left in buffer\n" + , m_receive_buffer_size); + TORRENT_ASSERT(m_read_buffer.empty()); + break; + } + +#if TORRENT_USE_INVARIANT_CHECKS + check_receive_buffers(); +#endif + + packet* const p = i->get(); + + // the number of bytes to copy is added to the 16 bit header field + // p->header_size, so we have to make sure we stay within that limit + int const to_copy = static_cast(std::min({ + std::ptrdiff_t(p->size - p->header_size) + , target->size() + , std::ptrdiff_t(std::numeric_limits::max() - p->header_size)})); + TORRENT_ASSERT(to_copy >= 0); + std::memcpy(target->data(), p->buf + p->header_size, std::size_t(to_copy)); + ret += std::size_t(to_copy); + TORRENT_ASSERT(target->size() >= to_copy); + *target = target->subspan(to_copy); + + TORRENT_ASSERT(m_receive_buffer_size >= to_copy); + m_receive_buffer_size -= to_copy; + TORRENT_ASSERT(m_read_buffer_size >= to_copy); + m_read_buffer_size -= to_copy; + p->header_size += std::uint16_t(to_copy); + if (target->size() == 0) target = m_read_buffer.erase(target); + +#if TORRENT_USE_INVARIANT_CHECKS + check_receive_buffers(); +#endif + + // Consumed entire packet + if (p->header_size == p->size) + { + release_packet(std::move(*i)); + i->reset(); + ++pop_packets; + ++i; + } + + TORRENT_ASSERT(m_receive_buffer_size >= 0); + if (m_receive_buffer_size <= 0) + { + UTP_LOGV("%8p: Didn't fill entire target: %d bytes left in buffer\n" + , static_cast(this), m_receive_buffer_size); + break; + } + } + // remove the packets from the receive_buffer that we already copied over + // and freed + m_receive_buffer.erase(m_receive_buffer.begin() + , 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_receive_buffer_size == 0 || m_read_buffer.empty()); + + UTP_LOGV("%8p: %d packets moved from buffer to user space (%d bytes)\n" + , static_cast(this), pop_packets, int(ret)); + + if (clear_buffers) + { + m_read_buffer_size = 0; + m_read_buffer.clear(); + } + TORRENT_ASSERT(ret > 0 || m_null_buffers); + return ret; +} + +void utp_socket_impl::issue_write() +{ + UTP_LOGV("%8p: new write handler. %d bytes to write\n" + , static_cast(this), m_write_buffer_size); + + TORRENT_ASSERT(m_write_buffer_size > 0); + TORRENT_ASSERT(m_write_handler == false); + TORRENT_ASSERT(m_userdata); + + m_write_handler = true; + m_written = 0; + if (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 (send_pkt()); + + maybe_trigger_send_callback(); +} + +std::size_t utp_socket_impl::write_some(bool const clear_buffers) +{ + m_written = 0; + + // try to write if the congestion window allows it + while (send_pkt()); + + if (clear_buffers) + { + m_write_buffer_size = 0; + m_write_buffer.clear(); + } + + return std::size_t(m_written); +} + +void utp_socket_impl::do_connect(tcp::endpoint const& ep) +{ + int const mtu = m_sm.mtu_for_dest(ep.address()); + init_mtu(mtu); + TORRENT_ASSERT(m_connect_handler == false); + m_remote_address = ep.address(); + m_port = ep.port(); + + m_connect_handler = true; + + if (test_socket_state()) return; + send_syn(); +} + +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 >= static_cast(state_t::error_wait) || state() == state_t::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 (state() == state_t::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 ((state() == state_t::error_wait + || state() == state_t::none + || state() == state_t::syn_sent) && cancelled) + { + set_state(state_t::deleting); +#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(state_t::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(state_t::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 + TORRENT_ASSERT(m_stalled); + m_stalled = false; + 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(state_t::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; + auto const incoming_close_reason = static_cast(aux::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 + std::ptrdiff_t write_buffer_size = 0; + for (auto const& i : m_write_buffer) + { + TORRENT_ASSERT(std::numeric_limits::max() - i.size() > write_buffer_size); + write_buffer_size += i.size(); + } + 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) + { + TORRENT_ASSERT(i != m_write_buffer.end()); + + // i points to the iovec we'll start copying from + int const to_copy = static_cast(std::min(std::ptrdiff_t(size), i->size())); + TORRENT_ASSERT(to_copy >= 0); + TORRENT_ASSERT(to_copy < INT_MAX / 2 && m_written < INT_MAX / 2); + std::memcpy(ptr, i->data(), std::size_t(to_copy)); + size -= to_copy; + m_written += to_copy; + ptr += to_copy; + *i = i->subspan(to_copy); + TORRENT_ASSERT(m_write_buffer_size >= to_copy); + m_write_buffer_size -= to_copy; + if (i->size() == 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() - j.size() > write_buffer_size); + write_buffer_size += j.size(); + } + 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. +// m_seq_nr is only incremented when sending packets with data payload. i.e. not +// for ST_STATE or ST_FIN packets +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(state() != state_t::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 (state() == state_t::error_wait || state() == state_t::deleting) 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_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; + + auto 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); + + 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 + , effective_mtu - p->size}); + + if (size_left > 0) { + 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 + && int(p->size) < std::min(int(p->allocated), effective_mtu) + && !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; + } + +#if TORRENT_USE_ASSERTS + payload_size = p->size - p->header_size; +#endif + } + + 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; + aux::write_uint32(close_reason, ptr); + } + + if (m_bytes_in_flight > 0 + && int(p->size) < std::min(int(p->allocated), effective_mtu) + && !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) && p->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; + 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(state_t::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(state_t::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(state_t const s) +{ + if (s == state()) return; + + m_sm.inc_stats_counter(counters::num_utp_idle + m_state, -1); + m_state = static_cast(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 + + TORRENT_ASSERT(size >= 0); + if (size <= 0) return; + + // if a packet, p, is passed in here, the buf argument is ignored, and size + // is redundant, as it must match p->size + TORRENT_ASSERT(!p || p->size - p->header_size == size); + TORRENT_ASSERT(!p || !buf); + + 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 = static_cast(std::min(std::ptrdiff_t(size), target->size())); + TORRENT_ASSERT(to_copy >= 0); + std::memcpy(target->data(), buf, std::size_t(to_copy)); + m_read += to_copy; + *target = target->subspan(to_copy); + buf += to_copy; + TORRENT_ASSERT(m_read_buffer_size >= to_copy); + m_read_buffer_size -= to_copy; + size -= to_copy; + UTP_LOGV("%8p: copied %d bytes into user receive buffer\n", static_cast(this), to_copy); + if (target->size() == 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(state() == state_t::error_wait || state() == state_t::deleting); + +#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(state_t::deleting); +#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 mtu) +{ + INVARIANT_CHECK; + + // set the ceiling to what we found out from the interface + m_mtu_ceiling = std::uint16_t(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 > mtu) m_mtu_floor = std::uint16_t(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 b + , udp::endpoint const& ep, time_point receive_time) +{ + INVARIANT_CHECK; + span const buf(reinterpret_cast(b.data()), b.size()); + + 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 (state() == state_t::none && ph->get_type() == ST_SYN) + { + m_remote_address = ep.address(); + m_port = ep.port(); + } + + if (state() != state_t::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 + // Note that when we send a FIN, we don't increment m_seq_nr + std::uint16_t const cmp_seq_nr = + ((state() == state_t::syn_sent || state() == state_t::fin_sent) + && ph->get_type() == ST_STATE) + ? m_seq_nr : (m_seq_nr - 1) & ACK_MASK; + + if ((state() != state_t::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 (state() != state_t::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 (state() != state_t::none + && state() != state_t::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(state_t::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 (state() != state_t::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 (state() == state_t::error_wait || state() == state_t::deleting) 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 (state() == state_t::error_wait || state() == state_t::deleting) 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 state_t::fin_sent. The sent FIN is also an ack + // to the FIN we received. Once we're in state_t::fin_sent we + // just need to wait for our FIN to be acked. + + if (state() == state_t::fin_sent) + { + send_pkt(pkt_ack); + if (state() == state_t::error_wait || state() == state_t::deleting) return true; + } + else + { + send_fin(); + if (state() == state_t::error_wait || state() == state_t::deleting) 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 (state()) + { + case state_t::none: + { + if (ph->get_type() == ST_SYN) + { + // if we're in state_none, the only thing + // we accept are SYN packets. + set_state(state_t::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 state_t::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(state_t::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 state_t::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 (state() == state_t::error_wait || state() == state_t::deleting) 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 state_t::fin_sent state. + send_fin(); + if (state() == state_t::error_wait || state() == state_t::deleting) 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 state_t::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; + } + + // we don't increment m_seq_nr when sending a FIN, so we actually + // need to wait for m_acked_sen_nr to reach m_seq_nr before the FIN + // is considered ACKed + if (m_acked_seq_nr == m_seq_nr) + { + // 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(state_t::deleting); + test_socket_state(); + } + else + { + UTP_LOGV("%8p: closing socket\n", static_cast(this)); + m_error = boost::asio::error::eof; + set_state(state_t::error_wait); + test_socket_state(); + } + } + + break; + } + case state_t::deleting: + case state_t::error_wait: + { + // 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 (state() == state_t::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 (state() == state_t::error_wait || state() == state_t::deleting) 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(state_t::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() + || (state() == state_t::syn_sent && p->num_transmissions >= m_sm.syn_resends()) + || (state() == state_t::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(state_t::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 (state() == state_t::error_wait || state() == state_t::deleting) return; + } + else if (m_state < static_cast(state_t::fin_sent)) + { + send_pkt(); + if (state() == state_t::error_wait || state() == state_t::deleting) return; + } + else if (state() == state_t::fin_sent) + { + // the connection is dead + m_error = boost::asio::error::eof; + set_state(state_t::error_wait); + test_socket_state(); + return; + } + } +} + +#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..6db34f2 --- /dev/null +++ b/src/version.cpp @@ -0,0 +1,42 @@ +/* + +Copyright (c) 2015, 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/version.hpp" + +namespace libtorrent { + +char const* version() +{ + return version_str; +} + +} diff --git a/src/web_connection_base.cpp b/src/web_connection_base.cpp new file mode 100644 index 0000000..13b9aeb --- /dev/null +++ b/src/web_connection_base.cpp @@ -0,0 +1,213 @@ +/* + +Copyright (c) 2010, 2013-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016, 2019, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/invariant_check.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/peer_info.hpp" + +namespace libtorrent { + + web_connection_base::web_connection_base( + peer_connection_args& pack + , web_seed_t const& 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; + +#if TORRENT_USE_SSL + 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 = m_host; + aux::verify_encoding(m_server_string); + } + + 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 const 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..8abad1b --- /dev/null +++ b/src/web_peer_connection.cpp @@ -0,0 +1,1243 @@ +/* + +Copyright (c) 2006-2020, Arvid Norberg +Copyright (c) 2016, Falcosc +Copyright (c) 2016-2017, 2020, Alden Torres +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016-2017, Andrei Kurushin +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/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/aux_/alert_manager.hpp" // for alert_manager +#include "libtorrent/aux_/escape_string.hpp" // for escape_path +#include "libtorrent/hex.hpp" // for is_hex +#include "libtorrent/random.hpp" +#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& 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() +{ + peer_id pid; + aux::random_bytes(pid); + set_pid(pid); + + 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); + } + t->set_seed(peer_info_struct(), false); + 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) post(get_context() + , 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())) + { + post(get_context(), 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 const& server_version = p.header("server"); + if (!server_version.empty()) + return server_version; + return host; + } + + 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] != '/'; + + // when SSRF mitigation is enabled, a web seed on the internet (is_global()) + // is not allowed to redirect to a server on the local network, so we set + // the no_local_ips flag + auto const web_seed_flags = torrent::ephemeral + | ((m_settings.get_bool(settings_pack::ssrf_mitigation) && aux::is_global(remote().address())) + ? torrent::no_local_ips : web_seed_flag_t{}); + + // 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, web_seed_flags); + 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, web_seed_flags); + + // 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()); + auto 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..58604ca --- /dev/null +++ b/src/write_resume_data.cpp @@ -0,0 +1,390 @@ +/* + +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2017-2018, 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 "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 +#include "libtorrent/aux_/merkle.hpp" // for merkle_ + +namespace libtorrent { +namespace { + entry build_tracker_list(std::vector const& trackers + , std::vector const& tracker_tiers) + { + entry ret; + entry::list_type& tr_list = ret.list(); + if (trackers.empty()) return ret; + + tr_list.emplace_back(entry::list_type()); + std::size_t tier = 0; + auto tier_it = tracker_tiers.begin(); + for (std::string const& tr : trackers) + { + if (tier_it != 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); + } + return ret; + } +} + + entry write_resume_data(add_torrent_params const& atp) + { + entry ret; + + using namespace libtorrent::aux; // for write_*_endpoint() + ret["file-format"] = "libtorrent resume file"; + ret["file-version"] = 1; + ret["libtorrent-version"] = lt::version_str; + 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 (!atp.name.empty()) ret["name"] = atp.name; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + if (!atp.url.empty()) ret["url"] = atp.url; +#endif + + ret["info-hash"] = atp.info_hashes.v1; + ret["info-hash2"] = atp.info_hashes.v2; + + if (atp.ti) + { + auto const info = atp.ti->info_section(); + ret["info"].preformatted().assign(info.data(), info.data() + info.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_trees.empty()) + { + auto& trees = atp.merkle_trees; + auto& ret_trees = ret["trees"].list(); + ret_trees.reserve(atp.merkle_trees.size()); + for (file_index_t f(0); f < file_index_t{int(atp.merkle_trees.size())}; ++f) + { + auto const& tree = trees[f]; + ret_trees.emplace_back(entry::dictionary_t); + auto& ret_dict = ret_trees.back().dict(); + auto& ret_tree = ret_dict["hashes"].string(); + + ret_tree.reserve(tree.size() * 32); + for (auto const& n : tree) + ret_tree.append(n.data(), n.size()); + + if (f < atp.verified_leaf_hashes.end_index()) + { + auto const& verified = atp.verified_leaf_hashes[f]; + if (!verified.empty()) + { + auto& ret_verified = ret_dict["verified"].string(); + ret_verified.reserve(verified.size()); + for (auto const bit : verified) + ret_verified.push_back(bit ? '1' : '0'); + } + } + + if (f < atp.merkle_tree_mask.end_index()) + { + auto const& mask = atp.merkle_tree_mask[f]; + if (!mask.empty()) + { + auto& ret_mask = ret_dict["mask"].string(); + ret_mask.reserve(mask.size()); + for (auto const bit : mask) + ret_mask.push_back(bit ? '1' : '0'); + } + } + } + } + + 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 + ret["trackers"] = build_tracker_list(atp.trackers, atp.tracker_tiers); + + // save web seeds + // if we removed the web seeds, make sure to record that in the resume + // data + 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) + { + auto 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.max_uploads; + + 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; + } + + entry write_torrent_file(add_torrent_params const& atp) + { + entry ret; + if (!atp.ti) + aux::throw_ex(errors::torrent_missing_info); + + auto const info = atp.ti->info_section(); + ret["info"].preformatted().assign(info.data(), info.data() + info.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_trees.empty()) + { + file_storage const& fs = atp.ti->files(); + auto& trees = atp.merkle_trees; + if (int(trees.size()) != fs.num_files()) + aux::throw_ex(errors::torrent_missing_piece_layer); + + auto& piece_layers = ret["piece layers"].dict(); + std::vector const empty_verified; + for (file_index_t f : fs.file_range()) + { + if (fs.pad_file_at(f) || fs.file_size(f) < fs.piece_length()) + continue; + + aux::merkle_tree t(fs.file_num_blocks(f) + , fs.piece_length() / default_block_size, fs.root_ptr(f)); + + std::vector const& verified = (f >= atp.verified_leaf_hashes.end_index()) + ? empty_verified : atp.verified_leaf_hashes[f]; + + auto const& tree = trees[f]; + if (f < atp.merkle_tree_mask.end_index() && !atp.merkle_tree_mask[f].empty()) + { + t.load_sparse_tree(tree, atp.merkle_tree_mask[f], verified); + } + else + { + t.load_tree(tree, verified); + } + + auto const piece_layer = t.get_piece_layer(); + if (int(piece_layer.size()) != fs.file_num_pieces(f)) + aux::throw_ex(errors::torrent_invalid_piece_layer); + + auto& layer = piece_layers[t.root().to_string()].string(); + + for (auto const& h : piece_layer) + layer += h.to_string(); + } + } + else if (atp.ti->v2()) + { + // we must have piece layers for v2 torrents for them to be valid + // .torrent files + aux::throw_ex(errors::torrent_missing_piece_layer); + } + + // save web seeds + if (!atp.url_seeds.empty()) + { + entry::list_type& url_list = ret["url-list"].list(); + std::copy(atp.url_seeds.begin(), atp.url_seeds.end(), std::back_inserter(url_list)); + } + + if (!atp.http_seeds.empty()) + { + entry::list_type& httpseeds_list = ret["httpseeds"].list(); + std::copy(atp.http_seeds.begin(), atp.http_seeds.end(), std::back_inserter(httpseeds_list)); + } + + // save trackers + if (atp.trackers.size() == 1) + ret["announce"] = atp.trackers.front(); + else if (atp.trackers.size() > 1) + ret["announce-list"] = build_tracker_list(atp.trackers, atp.tracker_tiers); + + 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..71d8180 --- /dev/null +++ b/src/xml_parse.cpp @@ -0,0 +1,171 @@ +/* + +Copyright (c) 2014-2019, Arvid Norberg +Copyright (c) 2016-2017, 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 "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; + auto const name_len = static_cast(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..26d58e6 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,62 @@ +cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR) # Configurable policies: <= CMP0097 + +# 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 + broadcast_socket.cpp + dht_server.cpp + main.cpp + make_torrent.cpp + peer_server.cpp + settings.cpp + setup_transfer.cpp + swarm_suite.cpp + test.cpp + test_utils.cpp + udp_tracker.cpp + web_seed_suite.cpp +) +target_link_libraries(test_common PUBLIC torrent-rasterbar) +if (MSVC) + target_compile_options(test_common PUBLIC + /wd4127 # C4127: conditional expression is constant + /wd4309 # C4309: 'conversion' : truncation of constant value + /wd4310 # C4310: cast truncates constant value + ) +endif() + +if(CMAKE_CXX_COMPILER_ID MATCHES Clang) + target_compile_options(test_common PUBLIC -Wno-implicit-int-float-conversion) +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 "mutable_test_torrents" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") +file(COPY "ssl" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") +file(COPY "test_torrents" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") +file(COPY "utf8_test.txt" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") diff --git a/test/Jamfile b/test/Jamfile new file mode 100644 index 0000000..f04062c --- /dev/null +++ b/test/Jamfile @@ -0,0 +1,320 @@ +import testing ; +import feature : feature ; + +use-project /torrent : .. ; + +lib libtorrent_test + : # sources + main.cpp + test.cpp + broadcast_socket.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 + clang:-Wno-implicit-int-float-conversion + @warnings + + : # default build + shared + 14 + + : # 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 + 14 + debug-mode +# default to 64 bit address model as it's required by mmap-disk-io. + 64 + ; + +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 + clang:-Wno-implicit-int-float-conversion + @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 : . ; + +install stage_dependencies + : /torrent//torrent + libtorrent_test + : dependencies + on + SHARED_LIB + : $(default-build) + ; + +explicit test_natpmp ; +explicit enum_if ; +explicit stage_enum_if ; +explicit stage_dependencies ; + +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_add_torrent.cpp ; +run test_info_hash.cpp ; +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_merkle_tree.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_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_stack_allocator.cpp ; +run test_file_progress.cpp ; +run test_generate_peer_id.cpp ; +run test_piece_picker.cpp ; +run test_alloca.cpp ; +run test_string.cpp ; +run test_utf8.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_apply_pad.cpp ; +run test_alert_types.cpp ; +run test_magnet.cpp ; +run test_storage.cpp ; +run test_store_buffer.cpp ; +run test_mmap.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_torrent_list.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_hash_picker.cpp ; +run test_torrent.cpp ; +run test_remap_files.cpp ; +run test_similar_torrent.cpp ; +run test_truncate.cpp ; +run test_copy_file.cpp ; + +# turn these tests into simulations +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_utp.cpp ; +run test_auto_unchoke.cpp ; +run test_http_connection.cpp : : + : openssl:/torrent//ssl + openssl:/torrent//crypto + ; +run test_transfer.cpp ; +run test_time_critical.cpp ; +run test_priority.cpp ; + +run test_upnp.cpp ; +run test_lsd.cpp ; +explicit test_lsd ; +run test_hasher.cpp ; +explicit test_hasher ; +run test_hasher512.cpp ; +explicit test_hasher512 ; + +# unfortunately, some tests spin up full libtorrent sessions, with threads and +# real sockets and sometimes fail for timing issues. This is a list of all the +# deterministic tests +alias deterministic-tests : + test_alert_manager + test_apply_pad + test_alert_types + test_alloca + test_bandwidth_limiter + test_bdecode + test_bencoding + test_bitfield + test_bloom_filter + test_buffer + test_crc32 + test_create_torrent + test_dht + test_dos_blocker + test_ed25519 + test_enum_net + test_fence + test_ffs + test_file + test_file_progress + test_file_storage + test_generate_peer_id + test_gzip + test_hash_picker + test_heterogeneous_queue + test_http_parser + test_identify_client + test_info_hash + test_io + test_ip_filter + test_ip_voter + test_listen_socket + test_magnet + test_merkle + test_merkle_tree + test_mmap + test_packet_buffer + test_part_file + test_pe_crypto + test_peer_classes + test_peer_list + test_peer_priority + test_piece_picker + test_primitives + test_read_resume + test_receive_buffer + test_recheck + test_remap_files + test_resolve_links + test_resume + test_session + test_session_params + test_settings_pack + test_sha1_hash + test_sliding_average + test_socket_io + test_span + test_stack_allocator + test_stat_cache + test_storage + test_string + test_tailqueue + test_threads + test_time + test_timestamp_history + test_torrent + test_torrent_info + test_torrent_list + test_utf8 + test_xml + test_store_buffer + test_similar_torrent + test_truncate + ; diff --git a/test/bittorrent_peer.cpp b/test/bittorrent_peer.cpp new file mode 100644 index 0000000..7620486 --- /dev/null +++ b/test/bittorrent_peer.cpp @@ -0,0 +1,563 @@ +/* + +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, 2018, 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/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_context.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/random.hpp" + +#include +#include +#include + +using namespace lt; +using namespace std::placeholders; + +peer_conn::peer_conn(io_context& 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::aux; + + 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::aux; + + // 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().c_str() + , s.local_endpoint(e).port()); + else + std::snprintf(ep_str, sizeof(ep_str), "%s:%d", addr.to_string().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::aux; + + 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::aux; + + 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 = aux::read_int32(ptr); + int start = aux::read_int32(ptr); + int length = aux::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 = aux::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 = aux::read_int32(ptr); + int const start = aux::read_int32(ptr); + + if ((start + int(bytes_transferred)) / 0x4000 == m_blocks_per_piece) + { + write_have(piece); + return; + } + } + else if (msg == 13) // suggest + { + int piece = aux::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 = aux::read_int32(ptr); + int start = aux::read_int32(ptr); + int length = aux::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 = aux::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::aux; + +// 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::aux; + + 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..1e2da6f --- /dev/null +++ b/test/bittorrent_peer.hpp @@ -0,0 +1,110 @@ +/* + +Copyright (c) 2016-2017, 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 BITTORRENT_PEER_HPP +#define BITTORRENT_PEER_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/io_context.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_context& 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/broadcast_socket.cpp b/test/broadcast_socket.cpp new file mode 100644 index 0000000..53b573b --- /dev/null +++ b/test/broadcast_socket.cpp @@ -0,0 +1,249 @@ +/* + +Copyright (c) 2015-2017, Steven Siloti +Copyright (c) 2007-2012, 2014-2020, Arvid Norberg +Copyright (c) 2016-2018, 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_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/socket.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" + +#include "broadcast_socket.hpp" + +using namespace std::placeholders; + +namespace libtorrent { + + broadcast_socket::broadcast_socket( + udp::endpoint multicast_endpoint) + : m_multicast_endpoint(std::move(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_context& ios, error_code& ec, bool loopback) + { + m_on_receive = std::move(handler); + + std::vector interfaces = enum_net_interfaces(ios, ec); + + if (aux::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() != aux::is_v4(m_multicast_endpoint)) continue; + // ignore any loopback interface + if (!loopback && i.interface_address.is_loopback()) 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_context& 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_context& 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/test/broadcast_socket.hpp b/test/broadcast_socket.hpp new file mode 100644 index 0000000..a5b750d --- /dev/null +++ b/test/broadcast_socket.hpp @@ -0,0 +1,143 @@ +/* + +Copyright (c) 2016, Steven Siloti +Copyright (c) 2007-2012, 2015-2020, Arvid Norberg +Copyright (c) 2016-2018, 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_BROADCAST_SOCKET_HPP_INCLUDED +#define TORRENT_BROADCAST_SOCKET_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" +#include "test.hpp" + +#include +#include +#include + +namespace libtorrent { + + using receive_handler_t = std::function buffer)>; + + class EXPORT broadcast_socket + { + public: + explicit broadcast_socket(udp::endpoint multicast_endpoint); + ~broadcast_socket() { close(); } + + void open(receive_handler_t handler, io_context& 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() + && aux::is_v4(socket->local_endpoint(ec)); + } + address_v4 broadcast_address() const + { + error_code ec; + return make_network_v4(socket->local_endpoint(ec).address().to_v4(), netmask).broadcast(); + } + }; + + void on_receive(socket_entry* s, error_code const& ec + , std::size_t bytes_transferred); + void open_unicast_socket(io_context& ios, address const& addr + , address_v4 const& mask); + void open_multicast_socket(io_context& 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/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..89fdd83 --- /dev/null +++ b/test/dht_server.cpp @@ -0,0 +1,183 @@ +/* + +Copyright (c) 2013-2019, Arvid Norberg +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/bencode.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/io_context.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_context 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().c_str(), 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.restart(); + } + + 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..39f82a1 --- /dev/null +++ b/test/enum_if.cpp @@ -0,0 +1,134 @@ +/* + +Copyright (c) 2018, Steven Siloti +Copyright (c) 2007-2008, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2018, 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 "libtorrent/enum_net.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" + +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_context 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().c_str() + , r.netmask.to_string().c_str() + , r.gateway.is_unspecified() ? "-" : r.gateway.to_string().c_str() + , r.mtu + , r.source_hint.is_unspecified() ? "-" : r.source_hint.to_string().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().c_str() + , i.netmask.to_string().c_str() + , i.name + , gateway ? gateway->to_string().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 unit_test::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; +#ifdef _WIN32 +bool redirect_stdout = !IsDebuggerPresent(); +#else +bool redirect_stdout = true; +#endif +// 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::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; +}; + +namespace unit_test { + +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 < ::unit_test::g_num_unit_tests; ++i) + { + std::printf(" - %s\n", ::unit_test::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\"\n", executable, unit_dir_prefix.c_str()); + + if (unit_test::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 < unit_test::g_num_unit_tests; ++i) + { + if (filter && tests_to_run.count(unit_test::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; + } + + auto& t = ::unit_test::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); + + ::unit_test::g_test_idx = i; + current_test = &t; + + std::printf("cwd: %s\n", unit_dir.c_str()); + std::printf("test-case: %s\n", t.name); + std::mt19937 rng(0x82daf973); + lt::aux::random_engine() = rng; + std::printf("rnd = %x\n", lt::random(0xffffffff)); + +#ifndef BOOST_NO_EXCEPTIONS + try + { +#endif + + std::srand(unsigned(std::hash{}(executable)) + unsigned(i)); + lt::aux::random_engine().seed(0x82daf973); + + ::unit_test::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()); + unit_test::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()); + unit_test::report_failure(buf, __FILE__, __LINE__); + } + catch (...) + { + unit_test::report_failure("TEST_ERROR: Terminated with unknown exception", __FILE__, __LINE__); + } +#endif + + if (!tests_to_run.empty()) tests_to_run.erase(t.name); + + if (::unit_test::g_test_failures > 0) + { + output_test_log_to_terminal(); + } + + t.num_failures = ::unit_test::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 unit_test::print_failures() ? 333 : 0; +} + diff --git a/test/make_torrent.cpp b/test/make_torrent.cpp new file mode 100644 index 0000000..b5e048f --- /dev/null +++ b/test/make_torrent.cpp @@ -0,0 +1,214 @@ +/* + +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016-2020, Arvid Norberg +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 + +#include "make_torrent.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/aux_/posix_storage.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; + } + + if (!args.m_collection.empty()) + { + auto& l = info["collections"].list(); + l.push_back(args.m_collection); + } + + 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; + bencode(std::back_inserter(tmp), 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) +{ + aux::vector priorities; + sha1_hash info_hash; + storage_params params{ + ti.files(), + nullptr, + path, + storage_mode_t::storage_mode_sparse, + priorities, + info_hash + }; + + // default settings + aux::session_settings sett; + aux::posix_storage st(params); + + 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(sett, b, i, 0, 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..faa7575 --- /dev/null +++ b/test/make_torrent.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2016-2017, 2019-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 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; } + torrent_args& collection(std::string c) { m_collection = c; return *this; } + + bool m_priv; + std::string m_name; + std::vector m_files; + std::string m_url_seed; + std::string m_http_seed; + std::string m_collection; +}; + +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..187a7d1 --- /dev/null +++ b/test/peer_server.cpp @@ -0,0 +1,170 @@ +/* + +Copyright (c) 2013, 2015-2019, Arvid Norberg +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/bencode.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/io_context.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_context 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().c_str(), 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.restart(); + } + + 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().c_str()); + ++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..2bbb021 --- /dev/null +++ b/test/peer_server.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2013, 2016, 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 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..291f4e7 --- /dev/null +++ b/test/print_alerts.cpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2016-2017, 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 "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..6cc29a3 --- /dev/null +++ b/test/print_alerts.hpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2016-2017, 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 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..588dc5c --- /dev/null +++ b/test/settings.cpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2015-2018, 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/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); + pack.set_int(settings_pack::hashing_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..321d31d --- /dev/null +++ b/test/settings.hpp @@ -0,0 +1,37 @@ +/* + +Copyright (c) 2015, 2017, 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 "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..72cbd76 --- /dev/null +++ b/test/setup_transfer.cpp @@ -0,0 +1,1240 @@ +/* + +Copyright (c) 2006-2020, Arvid Norberg +Copyright (c) 2015-2017, Alden Torres +Copyright (c) 2016-2018, Steven Siloti +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2017, 2020, AllSeeingEyeTolledEweSew +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/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/hex.hpp" // to_hex +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/aux_/merkle.hpp" +#include "libtorrent/disk_interface.hpp" // for default_block_size +#include "libtorrent/aux_/ip_helpers.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 + +#if defined TORRENT_WINDOWS +#define SEPARATOR "\\" +#else +#define SEPARATOR "/" +#endif + +std::shared_ptr generate_torrent(bool const with_files, bool const with_hashes) +{ + if (with_files) + { + error_code ec; + create_directories("test_resume", ec); + std::vector a(128 * 1024 * 8); + std::vector b(128 * 1024); + ofstream("test_resume/tmp1").write(a.data(), std::streamsize(a.size())); + ofstream("test_resume/tmp2").write(b.data(), std::streamsize(b.size())); + 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); + + 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); + if (with_hashes) + { + lt::set_piece_hashes(t, "." + , [] (lt::piece_index_t) {}); + } + else + { + for (auto const i : fs.piece_range()) + { + sha1_hash ph; + aux::random_bytes(ph); + t.set_hash(i, ph); + } + + for (piece_index_t i : fs.piece_range()) + { + sha256_hash ph; + aux::random_bytes(ph); + file_index_t const f(fs.file_index_at_piece(i)); + t.set_hash2(f, i - fs.piece_index_at_file(f), 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; + address_v6::bytes_type g_addr6 + = {0x93, 0x30, 0x2e, 0xf4, 0x1c, 0x01, 0x3d, 0x8a + , 0x35, 0x3d, 0x69, 0x10, 0x55, 0x82, 0x9d, 0x2f}; +} + +void init_rand_address() +{ + g_addr = 0x92343023; + g_addr6 = address_v6::bytes_type{ + {0x93, 0x30, 0x2e, 0xf4, 0x1c, 0x01, 0x3d, 0x8a + , 0x35, 0x3d, 0x69, 0x10, 0x55, 0x82, 0x9d, 0x2f}}; +} + +address rand_v4() +{ + address_v4 ret; + do + { + g_addr += 0x3080ca; + ret = address_v4(g_addr); + } while (ret.is_unspecified() || aux::is_local(ret) || ret.is_loopback()); + 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; +} + +namespace { +void add_mp(span target, span add) +{ + TORRENT_ASSERT(target.size() == add.size()); + int carry = 0; + for (int i = int(target.size()) - 1; i >= 0; --i) + { + int const res = carry + int(target[i]) + add[i]; + carry = res >> 8; + target[i] = std::uint8_t(res & 255); + } +} +} + +address rand_v6() +{ + static address_v6::bytes_type const add{ + {0x93, 0x30, 0x2e, 0xf4, 0x1c, 0x01, 0x3d, 0x8a + , 0x35, 0x3d, 0x69, 0x10, 0x55, 0x82, 0x9d, 0x23}}; + + address_v6 ret; + do + { + add_mp(g_addr6, add); + ret = address_v6(g_addr6); + + } while (ret.is_unspecified() || aux::is_local(ret) || ret.is_loopback()); + return ret; +} + +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); +} + +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_context 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 +} + +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().c_str(), 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_to_string(a->timestamp()).c_str() + , 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().c_str(), 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(5)); + } 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 wait_for_seeding(lt::session& ses, char const* name) +{ + time_point start = clock_type::now(); + bool seeding = false; + alert const* a = nullptr; + do + { + seeding = print_alerts(ses, name, true, true + , [](lt::alert const* al) + { + state_changed_alert const* sc = alert_cast(al); + return sc && sc->state == torrent_status::seeding; + }, false); + if (seeding) break; + if (total_seconds(clock_type::now() - start) > 10) break; + a = ses.wait_for_alert(seconds(5)); + } while (a); + if (!seeding) + { + std::printf("%s: did not receive a state_changed_alert indicating " + "the torrent is seeding. 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(nullptr, buf, nullptr, nullptr, TRUE + , 0, nullptr, nullptr, &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 == nullptr) 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_context 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(make_address("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::vector get_python() +{ + std::vector ret; +#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(), DWORD(buf.size())); + if (size_t(sz) == buf.size() - 1) ret.emplace_back(buf.data(), buf.size()); + } +#endif + ret.push_back("python3"); + ret.push_back("python"); + return ret; +} + +int find_available_port() +{ + int port = 2000 + (std::int64_t(::getpid()) + ::unit_test::g_test_idx + std::rand()) % 60000; + error_code ec; + io_context 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(make_address("127.0.0.1") + , std::uint16_t(port)), ec); + } while (ec); + return port; +} +} // anonymous namespace + +// 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 const port = find_available_port(); + + char const* type = ""; + char const* auth = ""; + char const* cmd = ""; + + switch (proxy_type) + { + case settings_pack::socks4: + type = "socks4"; + auth = " --allow-v4"; + cmd = ".." SEPARATOR "socks.py"; + break; + case settings_pack::socks5: + type = "socks5"; + cmd = ".." SEPARATOR "socks.py"; + break; + case settings_pack::socks5_pw: + type = "socks5"; + auth = " --username testuser --password testpass"; + cmd = ".." SEPARATOR "socks.py"; + break; + case settings_pack::http: + type = "http"; + cmd = ".." SEPARATOR "http_proxy.py"; + break; + case settings_pack::http_pw: + type = "http"; + auth = " --basic-auth testuser:testpass"; + cmd = ".." SEPARATOR "http_proxy.py"; + break; + } + std::vector python_exes = get_python(); + for (auto const& python_exe : python_exes) + { + 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().c_str(), port, type, auth); + std::printf("%s\n", buf); + pid_type r = async_run(buf); + if (r == 0) continue; + proxy_t t = { r, proxy_type }; + running_proxies.insert(std::make_pair(port, t)); + std::printf("%s launched\n", time_now_string().c_str()); + std::this_thread::sleep_for(lt::milliseconds(500)); + wait_for_port(port); + return port; + } + abort(); +} + +using namespace lt; + +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(aux::calc_num_pieces(fs)); + + 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); + return make_torrent(fs); +} + +std::shared_ptr make_torrent(lt::file_storage& fs) +{ + lt::create_torrent ct(fs, fs.piece_length()); + + for (auto const i : fs.piece_range()) + { + std::vector piece = generate_piece(i, fs.piece_size(i)); + ct.set_hash(i, hasher(piece).final()); + + aux::vector tree(merkle_num_nodes(fs.piece_length() / default_block_size)); + + int const blocks_per_piece = fs.piece_length() / default_block_size; + for (int j = 0; j < int(piece.size()); j += default_block_size) + { + tree[tree.end_index() - blocks_per_piece + j / default_block_size] + = hasher256(piece.data() + j, std::min(default_block_size, int(piece.size()) - j)).final(); + } + merkle_fill_tree(tree, fs.piece_length() / default_block_size); + file_index_t const f(fs.file_index_at_piece(i)); + ct.set_hash2(f, i - fs.piece_index_at_file(f), tree[0]); + } + + 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); + ofstream f(full_path.c_str()); + std::int64_t offset = 0; + while (to_write > 0) + { + int const s = std::min(to_write, static_cast(random_data.size())); + f.write(random_data.data(), s); + 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, lt::create_flags_t const flags + , 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, flags); + 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'; + + if (!(flags & create_torrent::v2_only)) + { + // 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 (!(flags & create_torrent::v1_only)) + { + int const blocks_in_piece = piece_size / default_block_size; + aux::vector v2tree(merkle_num_nodes(merkle_num_leafs(blocks_in_piece))); + for (int i = 0; i < blocks_in_piece; ++i) + { + sha256_hash const block_hash = hasher256(span(piece).subspan(i * default_block_size, default_block_size)).final(); + v2tree[v2tree.end_index() - merkle_num_leafs(blocks_in_piece) + i] = block_hash; + } + merkle_fill_tree(v2tree, merkle_num_leafs(blocks_in_piece)); + + for (piece_index_t i(0); i < t.files().end_piece(); ++i) + t.set_hash2(file_index_t{ 0 }, i - 0_piece, v2tree[0]); + } + + if (file) + { + while (total_size > 0) + { + file->write(piece.data(), 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, create_flags_t const flags) +{ + 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(make_address_v4("0.0.0.0") + , make_address_v4("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"); + ofstream file(file_path.c_str()); + t = ::create_torrent(&file, "temporary", piece_size, 9, false, flags); + 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_hashes().v2).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 = 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 = 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_hashes = t->info_hashes(); + } + else if (torrent2) + { + param.ti = *torrent2; + } + else + { + param.ti = 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().c_str(), port); + } + + if (port == 0) + { + port = ses2->listen_port(); + std::printf("%s: ses2->listen_port(): %d\n", time_now_string().c_str(), port); + } + + std::printf("%s: ses1: connecting peer port: %d\n" + , time_now_string().c_str(), port); + tor1.connect_peer(tcp::endpoint(make_address("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( + make_address("127.0.0.1", ec), std::uint16_t(port))); + std::printf("ses3: connecting peer port: %d\n", port2); + tor3.connect_peer(tcp::endpoint( + make_address("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 const port = find_available_port(); + + std::vector python_exes = get_python(); + + for (auto const& python_exe : python_exes) + { + char buf[200]; + std::snprintf(buf, sizeof(buf), "%s .." SEPARATOR "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().c_str(), port); + + std::printf("%s\n", buf); + pid_type r = async_run(buf); + if (r == 0) continue; + web_server_pid = r; + std::printf("%s launched\n", time_now_string().c_str()); + std::this_thread::sleep_for(lt::milliseconds(1000)); + wait_for_port(port); + return port; + } + abort(); +} + +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(make_address(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(make_address(ip, ec), std::uint16_t(port)); + TEST_CHECK(!ec); + return ret; +} + +lt::address addr(char const* ip) +{ + lt::error_code ec; + auto ret = lt::make_address(ip, ec); + TEST_CHECK(!ec); + return ret; +} + +lt::address_v4 addr4(char const* ip) +{ + lt::error_code ec; + auto ret = lt::make_address_v4(ip, ec); + TEST_CHECK(!ec); + return ret; +} + +lt::address_v6 addr6(char const* ip) +{ + lt::error_code ec; + auto ret = lt::make_address_v6(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..2ac5b25 --- /dev/null +++ b/test/setup_transfer.hpp @@ -0,0 +1,129 @@ +/* + +Copyright (c) 2006-2009, 2011, 2013-2020, Arvid Norberg +Copyright (c) 2015, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (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/create_torrent.hpp" +#include "libtorrent/fwd.hpp" + +EXPORT std::shared_ptr generate_torrent(bool with_files = false, bool with_hashes = false); + +EXPORT int load_file(std::string const& filename, std::vector& v + , lt::error_code& ec, int limit = 8000000); + +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); + +// determines if the operating system supports IPv6 +EXPORT bool supports_ipv6(); + +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 void wait_for_seeding(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 std::shared_ptr make_torrent(lt::file_storage& fs); +EXPORT void create_random_files(std::string const& path, lt::span file_sizes + , lt::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, lt::create_flags_t flags = {}, 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 + , lt::create_flags_t flags = {}); + +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..000e0a9 --- /dev/null +++ b/test/ssl/dhparams.pem @@ -0,0 +1,13 @@ +-----BEGIN DH PARAMETERS----- +MIICCAKCAgEA2bK8rXIrxYfHPhdWmWzD3+Q3nGJFP7hkKsuKeVFSU6ECvH/h48RN +l18QDp0eoDaVUrqp4Mal0batUgtRHjJxa6iq4F34LXEaynFDTPSgii38AVb/kiSl +gsLjKs+Qex7rvwNX3mV65fjkegtpkolHT7ynv90rV178SelvIxnLW3A+ixEcmpCU +ZVst6cJidg6KEQ7UfDEGlEgKYDmAuuxr69VKRbC1wwkfys1kY2vpJhBBjjNetcBM +tpT+EHXmePLTZejUOAtup3UCgHSRxrKhTSUC65gU7HblCspML5WdMafRhbnEYXiZ +1jKdSo4XREV6HQeT91eCHhaGcmzqfrKh/f6Ql4eaa10TMuy7PRxDIuce2RUEdUxo +m4bhnxw3L5ABq2mo4PqdeVTxnnAzsje+jgQ6WwPAG/AoCpVPiLhTyfdfPzFlj732 +oCCsMvcaR93YhUF0QELjM3FThIwpHViD+WKBWdGfZSwdE9PFUmxwSIx6Vnmd4AdV +xLXFWDnpQm8qAYN+7mxpB1W9OmmtQurqqLni/SS80g3kEyFAOfDp1ivcKOZ76xlt +9ayicTlPIfA25ffyls+UiL/sLbPopYQf3rWPaMLCXwuM8qfcsgvZfhyksERODx8c +KXB/15LF4gIfyZFP2VaBKcyh2rc3bbrfzKufLBqO9U/eH95B4Ngkf0sCAQI= +-----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..d575c70 --- /dev/null +++ b/test/ssl/invalid_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/invalid_peer_private_key.pem b/test/ssl/invalid_peer_private_key.pem new file mode 100644 index 0000000..226751c --- /dev/null +++ b/test/ssl/invalid_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/peer_certificate.pem b/test/ssl/peer_certificate.pem new file mode 100644 index 0000000..72ec223 --- /dev/null +++ b/test/ssl/peer_certificate.pem @@ -0,0 +1,82 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 53:ec:a5:6d:e0:33:0b:3f:1b:64:64:9f:8c:a2:65:7e:c1:9b:c5:ca + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=test + Validity + Not Before: Jun 12 13:35:50 2021 GMT + Not After : Jun 12 13:35:50 2022 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:d2:50:62:10:8e:4b:e9:fa:74:e7:53:97:ea:25: + b1:26:53:5a:ed:f6:a6:72:5e:9a:0f:8a:09:e2:b2: + dd:f9:bc:10:b1:de:9a:bd:e6:64:d9:0f:c1:b3:ed: + 42:d8:f7:9f:52:f6:49:38:1d:5d:f9:cc:8c:7e:c8: + ef:8e:f7:e8:e1:7c:9c:13:e1:32:72:a7:b9:df:68: + 1f:9d:88:82:09:4c:e0:7c:bb:9c:74:bf:9d:51:57: + f3:94:f8:80:d4:80:5f:ee:1c:98:a5:43:08:25:42: + b2:9b:1b:2a:2a:13:86:80:68:f8:ea:5d:d1:d7:50: + 9f:35:49:f9:9c:0b:a8:17:23:b1:b9:26:6c:32:00: + 82:3c:51:f2:bb:c6:1a:3d:5e:14:ce:1d:b4:b9:98: + cb:54:3a:21:f5:d1:40:75:c5:05:86:3b:ed:7e:87: + 79:ba:74:56:46:65:48:41:d0:9e:23:7b:dd:c7:b4: + c0:df:36:c8:01:03:b6:96:03:27:bd:6c:8e:64:33: + 5a:f8:dd:49:7f:c2:d1:d2:a8:c7:3f:5e:63:78:28: + 3b:d4:c4:87:fe:13:16:96:f4:89:81:c0:fc:98:8a: + 54:8a:14:21:7f:7b:21:fc:f5:c8:37:0e:94:6b:a2: + a5:53:7e:13:27:cf:6b:a5:0b:f8:f4:53:fa:5e:6d: + d3:25 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 2C:FE:68:9B:AD:6D:20:F5:37:62:AA:3C:FE:D8:70:94:47:75:87:1D + X509v3 Authority Key Identifier: + keyid:AA:B5:9C:D4:9C:C9:0C:A7:C8:55:0C:9E:98:EE:55:03:52:00:D9:08 + + Signature Algorithm: sha256WithRSAEncryption + 97:36:30:a3:b2:44:1f:d2:cc:27:64:22:85:31:9d:03:93:10: + 7b:bc:dc:05:76:49:aa:71:a1:70:11:2c:0d:63:6a:5f:0b:9e: + 3a:eb:f0:dc:d8:32:54:c5:13:3b:a0:76:b5:9b:f6:62:f7:1a: + 7b:b7:a4:2b:b4:0e:b3:f2:86:fa:22:94:ab:0f:34:2d:10:98: + 8a:62:c2:25:33:12:45:96:ae:e9:95:74:49:5b:86:5d:42:a5: + b7:b3:ca:e2:1d:9a:a5:81:09:ff:39:ee:fa:98:81:a7:ad:85: + 69:1a:ef:d1:73:15:04:b1:82:6e:8f:5b:5b:f7:03:16:0b:47: + df:a5:c6:78:26:c6:2a:09:09:3a:9b:8b:ee:2e:3e:14:f7:97: + 4d:af:1b:d0:8f:07:7c:e2:6b:5a:b5:d0:3b:cc:e6:8e:97:81: + 7b:eb:82:63:f5:cc:42:59:4b:47:ff:2b:ec:f8:0a:55:a5:b6: + 15:ba:1d:34:f5:59:11:dd:2a:13:72:b2:98:40:66:45:00:bf: + b3:65:17:6b:c2:15:ca:53:0a:01:26:c3:8f:09:cf:56:78:33: + 41:cc:2a:62:cd:38:9d:16:a3:ec:cf:f5:59:bf:5f:ab:2b:2a: + e6:12:f5:27:f5:30:9d:80:fb:20:4d:a6:f6:a5:6f:48:e9:59: + dc:8c:3c:72 +-----BEGIN CERTIFICATE----- +MIIDrjCCApagAwIBAgIUU+ylbeAzCz8bZGSfjKJlfsGbxcowDQYJKoZIhvcNAQEL +BQAwVDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDENMAsGA1UEAwwEdGVzdDAeFw0yMTA2 +MTIxMzM1NTBaFw0yMjA2MTIxMzM1NTBaMFExCzAJBgNVBAYTAkFVMRMwEQYDVQQI +DApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQx +CjAIBgNVBAMMASowggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDSUGIQ +jkvp+nTnU5fqJbEmU1rt9qZyXpoPignist35vBCx3pq95mTZD8Gz7ULY959S9kk4 +HV35zIx+yO+O9+jhfJwT4TJyp7nfaB+diIIJTOB8u5x0v51RV/OU+IDUgF/uHJil +QwglQrKbGyoqE4aAaPjqXdHXUJ81SfmcC6gXI7G5JmwyAII8UfK7xho9XhTOHbS5 +mMtUOiH10UB1xQWGO+1+h3m6dFZGZUhB0J4je93HtMDfNsgBA7aWAye9bI5kM1r4 +3Ul/wtHSqMc/XmN4KDvUxIf+ExaW9ImBwPyYilSKFCF/eyH89cg3DpRroqVTfhMn +z2ulC/j0U/pebdMlAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8W +HU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQs/mibrW0g +9Tdiqjz+2HCUR3WHHTAfBgNVHSMEGDAWgBSqtZzUnMkMp8hVDJ6Y7lUDUgDZCDAN +BgkqhkiG9w0BAQsFAAOCAQEAlzYwo7JEH9LMJ2QihTGdA5MQe7zcBXZJqnGhcBEs +DWNqXwueOuvw3NgyVMUTO6B2tZv2Yvcae7ekK7QOs/KG+iKUqw80LRCYimLCJTMS +RZau6ZV0SVuGXUKlt7PK4h2apYEJ/znu+piBp62FaRrv0XMVBLGCbo9bW/cDFgtH +36XGeCbGKgkJOpuL7i4+FPeXTa8b0I8HfOJrWrXQO8zmjpeBe+uCY/XMQllLR/8r +7PgKVaW2FbodNPVZEd0qE3KymEBmRQC/s2UXa8IVylMKASbDjwnPVngzQcwqYs04 +nRaj7M/1Wb9fqysq5hL1J/UwnYD7IE2m9qVvSOlZ3Iw8cg== +-----END CERTIFICATE----- diff --git a/test/ssl/peer_private_key.pem b/test/ssl/peer_private_key.pem new file mode 100644 index 0000000..211b010 --- /dev/null +++ b/test/ssl/peer_private_key.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIbgK6pdYRqXECAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECAbZQRhGG/qyBIIEyCq9SEHTmnEk +OsaBZaeoHfjG8dzZY2axOELULAmDQdVzedp9rJqfuj42SkVs8MVfVY6Y6wprd1Ik +48uOSwFyLECsWhRpSheIjX5ae6WbQv0IefguqLm/CnaAJs1YBUOC7ZnzJ8KVP3eB +5DaZK9nkpqarPXLUoXB8GXxQK/v8K1Q3gmnVbbXjyfo+nOCk5SXrpZnSwJTuEGxn +HEbT0TVOZBFrm8uc4MHd/EcbVvfckKWCtdL61PSx3SCvRcyoMoKXRnKHuhrgWMFo +6mer6E0GhVUSK7U2CHj8mL+UCHwc4pBYG7stxS0HmwBcUra/hP7/Ab9AjFyMOJ9+ +wxW3ocyJjp7mMrk3GAvPojM0rv9QSxOGk/CN9aavp1+xLh6S5vMgE66iY4H4BbDk +cc2uqK3MLHaw5e9PhoHli0/Q0tIoi47z7chrlfesfhMMpj+QbfXxjcvMqOE/AXq7 +JHrNNMJI3iYLDK2MD2gCuI0bMeV+KmacVoHN34SNNm4Ojl3cKkjdFVaVZEtyedgk +BWZmR4czO4RVK6ISiygLCAl9PmCrMOXORYtwxudJ3i2YDR/DWVo7syJ1HakwS4Li +UZv7u+jTlbV2sHNYdjs3xD/ozKe1JzNY5PEeakqdLmUvN8sVQrW+lcgWTN4ChT+9 +UHoOHSMRFlcQ8kAkxuJbCo4oJgpf5p2kWD1I13CGV8xYMnnHiLWyAKF00M3BUMF2 +hsoFYoWIedzpptSdjvCEpe5NWg+t3wks5K9pEiYywvYQhRUB66UdUNTcAYNLkt+S +fCLULmYN3Cbz645lhMqiBn3vEM378EDSTvjnk5LXWl9VUguV5cUvkM0GKDPHJY/i +I9DUnCtv2d9c5unm1muGKByIxF5lY7DZdeCqkKSG68KRoX2KcYgt+UszohdY2hCD +740xFMAORlZLW+PyKamCELjxtkpu+nGT/F3V2L0RguzQicDqNfpXr5ZOjnAp1z1x +z++yMt4SF/YEBEZ58o7KnCqcPeAWCJVxf/leGDqp/iFnqgzoL/bPSR2xcvQv21ll +luZEHoQd+89QaAQRSH4PdGweF+Ev3n+vt3qtgyrzeX3uiTT3zOv5ryRpVR3ghj5I +tsFvqw5U0GxGyxA6UichG24N9kOEyPvEubZ9ICxTXyueIoLNv/JO/9b9Sm8VpDa6 +V+hEF2KTCFCp49LMCk+C/Vh1ZE7MNDubuwV/a/uHkXqSc/eqwcAglNyJGNDRVGvj +dkJO0KvNs2qghwX/f0nJp9AaRYlK7euxpKEtp0EHinV+m24XsNwCn9RQULvAJ9ic +Et63uGdT5LiC1dXkarHB2/uX2MFE2TjSvM/nBeFLVu7ZfTgGCrk0XxeH/Z6dGmer +m+Hwu6dWnHyU2nuuQ1I7tGMKNrLv0ei/gqIkvBvT+U3QNTP1oJNikGp5bclsRXGJ +BgHLmfxuS0gXrPn4l5uG/jtc63Wy61mnSelBh2kYriKU3AZHt8JVfWgC8YjLqYjR +XqZYQDENraJtdETug6t+j4rGfYYnkxAXfnZZseRgSVKYxhVS9DDPOwlpHHQNDgIz +e91JplxL91m6kokgYIr4onOxrfqlxLujjnwjQ4S013Rz8qRauojjC1+fj8rP2aT9 +2KrzWO4fDTiL5SLAckaXww== +-----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..81057f5 --- /dev/null +++ b/test/ssl/root_ca_cert.pem @@ -0,0 +1,79 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 53:ec:a5:6d:e0:33:0b:3f:1b:64:64:9f:8c:a2:65:7e:c1:9b:c5:c9 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=test + Validity + Not Before: Jun 12 13:35:34 2021 GMT + Not After : Jun 11 13:35:34 2024 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:e7:96:ee:23:b3:24:f0:ea:3d:74:50:df:03:d7: + 20:6b:30:2f:2a:75:10:6a:c5:ae:23:3e:fa:46:25: + 7e:24:2c:c0:31:8d:22:69:ca:56:c4:62:bf:1d:d4: + 7a:27:43:99:e3:6f:73:e5:49:51:f1:5f:b9:a1:11: + ef:e7:68:76:53:cb:f3:3d:05:02:1d:79:14:cf:09: + 6a:79:74:c0:4a:a8:1d:b9:33:34:ca:37:d4:d9:11: + bd:80:3a:c5:ed:ba:6f:68:ca:71:d5:b7:28:dd:5a: + 7e:26:17:7f:3f:a0:db:94:03:e6:90:b9:6e:f5:67: + ef:5b:e6:43:a1:e5:a2:df:12:0e:66:d8:4f:eb:a1: + 75:86:47:c8:bf:17:ba:86:ac:9d:d6:54:dc:2d:fe: + 7f:09:64:11:7d:62:1f:cc:b7:cd:06:90:0a:eb:6a: + bd:e4:fc:1f:a4:6f:62:11:47:df:9a:9f:14:8b:20: + 32:cf:a2:df:91:b7:f3:7e:37:aa:6c:13:de:a5:8c: + 00:2e:06:83:85:f8:44:78:f3:9b:58:21:4e:3f:3c: + be:f0:53:db:3d:36:4f:c8:69:f0:4f:0a:4e:0a:5e: + 6f:d9:ec:bb:46:d9:0a:04:d8:8f:eb:4f:4a:97:0f: + c6:de:e7:aa:9b:c5:a5:87:18:3c:54:04:11:e6:ee: + 54:e3 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + AA:B5:9C:D4:9C:C9:0C:A7:C8:55:0C:9E:98:EE:55:03:52:00:D9:08 + X509v3 Authority Key Identifier: + keyid:AA:B5:9C:D4:9C:C9:0C:A7:C8:55:0C:9E:98:EE:55:03:52:00:D9:08 + + X509v3 Basic Constraints: critical + CA:TRUE + Signature Algorithm: sha256WithRSAEncryption + dd:df:ac:b9:74:18:72:4c:ce:0a:64:ca:83:4a:32:4d:53:dc: + 5e:b6:2e:9f:9e:ff:74:53:2c:fc:4f:55:57:50:6c:06:e2:5e: + ad:1c:b8:a9:b9:3f:3c:6b:ac:08:bb:65:24:94:3a:5b:07:6e: + 94:49:6b:ad:fa:be:14:c6:4c:01:34:96:14:34:da:2c:0e:78: + 4b:b5:e9:5c:47:1b:e0:3f:14:3f:a6:7b:f7:0c:50:75:79:62: + 0d:dd:6c:04:df:a6:7a:2d:e0:54:06:fd:44:43:97:2f:c3:0e: + c9:6b:ea:dc:de:d8:0c:da:51:47:79:d0:95:cf:45:86:8a:89: + 4a:18:ac:3f:05:c3:e8:68:c1:70:26:31:b4:ed:fa:80:64:8b: + 8c:fd:fc:a4:88:ad:e6:13:6f:d7:db:b2:4f:dc:2e:69:a0:45: + 92:6b:62:c9:2d:a2:52:4f:32:e2:83:7d:d5:e0:42:35:fc:2b: + 8e:e7:bc:86:51:ce:2b:41:be:57:9d:10:1b:ae:7f:6e:02:20: + e0:a1:b9:c3:2d:2b:f7:2d:86:37:5c:1f:0c:3b:9f:b4:18:7c: + cb:ab:67:0c:9e:a0:74:2f:5f:52:e6:54:2d:ac:c1:9c:64:41: + 40:77:63:a8:41:b4:16:54:b9:3e:ad:34:65:e8:77:f7:80:73: + 1e:b8:c7:0c +-----BEGIN CERTIFICATE----- +MIIDiTCCAnGgAwIBAgIUU+ylbeAzCz8bZGSfjKJlfsGbxckwDQYJKoZIhvcNAQEL +BQAwVDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDENMAsGA1UEAwwEdGVzdDAeFw0yMTA2 +MTIxMzM1MzRaFw0yNDA2MTExMzM1MzRaMFQxCzAJBgNVBAYTAkFVMRMwEQYDVQQI +DApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQx +DTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDn +lu4jsyTw6j10UN8D1yBrMC8qdRBqxa4jPvpGJX4kLMAxjSJpylbEYr8d1HonQ5nj +b3PlSVHxX7mhEe/naHZTy/M9BQIdeRTPCWp5dMBKqB25MzTKN9TZEb2AOsXtum9o +ynHVtyjdWn4mF38/oNuUA+aQuW71Z+9b5kOh5aLfEg5m2E/roXWGR8i/F7qGrJ3W +VNwt/n8JZBF9Yh/Mt80GkArrar3k/B+kb2IRR9+anxSLIDLPot+Rt/N+N6psE96l +jAAuBoOF+ER485tYIU4/PL7wU9s9Nk/IafBPCk4KXm/Z7LtG2QoE2I/rT0qXD8be +56qbxaWHGDxUBBHm7lTjAgMBAAGjUzBRMB0GA1UdDgQWBBSqtZzUnMkMp8hVDJ6Y +7lUDUgDZCDAfBgNVHSMEGDAWgBSqtZzUnMkMp8hVDJ6Y7lUDUgDZCDAPBgNVHRMB +Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQDd36y5dBhyTM4KZMqDSjJNU9xe +ti6fnv90Uyz8T1VXUGwG4l6tHLipuT88a6wIu2UklDpbB26USWut+r4UxkwBNJYU +NNosDnhLtelcRxvgPxQ/pnv3DFB1eWIN3WwE36Z6LeBUBv1EQ5cvww7Ja+rc3tgM +2lFHedCVz0WGiolKGKw/BcPoaMFwJjG07fqAZIuM/fykiK3mE2/X27JP3C5poEWS +a2LJLaJSTzLig33V4EI1/CuO57yGUc4rQb5XnRAbrn9uAiDgobnDLSv3LYY3XB8M +O5+0GHzLq2cMnqB0L19S5lQtrMGcZEFAd2OoQbQWVLk+rTRl6Hf3gHMeuMcM +-----END CERTIFICATE----- diff --git a/test/ssl/root_ca_private.pem b/test/ssl/root_ca_private.pem new file mode 100644 index 0000000..cfe033a --- /dev/null +++ b/test/ssl/root_ca_private.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIzUoyoTlcopQCAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECAU3RngL+9FQBIIEyHpgg5xaCQ6G +vlcVmcQ7XsMEit8FynYRjF4tGHdIwbTYI78MU5xkoCwMjjMKbKpC98VArtoSMZSk +4Wf24UomR7svpeBtkjc/E5lULgzXPUhokH9FmIvh7hkS8aGt2fE8p1JqKOK+6P7g +I74RKhP+Vx6MTuVzUbeyji9omdI6Av95ux9ECYBC/unEphXlEX4jEFiTzMRvNgH1 +iV4BZjwevuNsOJkhLJmFFVBv97/tFkd48I6MqROVrQQlDyQCWcDTd2hgavZURj4G +L+usJEwE/5mdwCmzm7cEhyaMYfFJ/nTn9hGrxRxrVvC1faBXdVa9x0rKWWH7zPwD +y8B+BurA31ofTJZrH9dM+D4vS9UGhHjNFPMQCQtokVFODSdy079Ef/E3fsIBi3Hr +6FZO0pEHB67wqrhjmovgOQZt7LUDJoDGo9cOWTBetdNrBNjMn+2ND/mIkL13J19K +cNOn5yS+lBFZJdpGxJHmL7Fq9MM29mGhYy4irDLkHTdumNwM5FpPQ7CFXStvh/QN +JU5LvEZNkGBaxkVGs505a79lzCNiv3nXSyGKGa7jKIxpPe4yyV66+uCWku10PGdV +CJFa1x9JS1+PlKwXCKqttrqQVFa9Vx/KaQagDg4pGwEBUwtTg9Y2NWXCBH5k/hou +Ahb07hVLVSoMJSNspESv07C3mZHOkgO4Axs+uEUKZqzhRghDIaNaRw5r/dWaqxY+ ++o391Hwx7z+llzMszkt1+F/ze/DLOPXKUCjOxt/V05+9H9HJk2XCZ09u2nQnq8Sd +GLX4er6CL5naxHh+B5QRd08iNrYWGRzkZESyiB4GN9mFHS54k+L9fQMoAlJMZu+B +XZwGgdkYQ1YbaCLLuuftPNytRk4zuYYxux7HKzrdXmH7ehFxRM2D95A3fzULtYlv +nXc5foPKFUbOUJ+ZuqClfYcyWJ+R5+FJIALGslK9Zszizb2dmZ0XLIDC8EW9PD8X +UMKwRigFZ9+ulHj0EXdljV2iFZh97GWnpJsHJKJBykfcdGaW+6f68jpclvWEhYok +p3K1/JLXNYkC9vnqiVirUhsdZbpjp/mUyfThwT9LI+jJR0iQpemlRIyj4CnELqlv +1RS4vN53MCO0mZFd04mndau4t39MWzyeNAskclqlpsphL79uYAXIffOo1zSqjtEQ +c+TmhlmJxQLNXoZ7hGC8TspG6J4xRewFV9KdifKRRnG+8nhxDqdOUek8WezWOdNH +bqefEfwel6KklBOJ6YYkKPVV5UJQ3ENLLsd7Bbf6gAcoYjyhFeQiBgswSUoFfoLU +slHOWSg01G7kOwqVl+srVjbnVNNE48v/N1G+mUjToKwWUgqOnu29JXim2tICD1MD +xrx4qrXTUdx4mxBtnqHIi2cxXoQeTlDQiDHZWqe+brAbsfT9tAgS855V/FlcDiev +i0iJCVN4o1/aC956UVHpHuE/UJUKs8PxeL3c9+uJ48T9lrxXC3oQt70chgrRtscL +YORSUMM/EmwaJ7okluefWpqmsFbIHGV3eUZJijk6RpWzP7tL227jkO7puUr/HoZD +GpqipQX8HhrVwPpQYpCSjDHRhdxqU2HqkDsU2/tNhHGK6SsVWRHyl8tgiDDefkgu ++CzZ04hwpfQjoPomYhp+sw== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/test/ssl/server.pem b/test/ssl/server.pem new file mode 100644 index 0000000..381d943 --- /dev/null +++ b/test/ssl/server.pem @@ -0,0 +1,84 @@ +-----BEGIN PRIVATE KEY----- +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 +Bkinmk7Wn7d94Jn3wO7Hg1FFrKumUaRa +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIUCsGc60dOoiJcatZSQnAxnwju+kUwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEKMAgGA1UEAwwBKjAeFw0yMTA2MTIx +MzM1MjFaFw0yNDA2MTMxMzM1MjFaMFExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApT +b21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxCjAI +BgNVBAMMASowggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDad7HG/Gp4 +c+j8VBnlMIQYHs0lNFYxdSe9cQ6d+Eww7yakehn0rpW6DPjsQOcvpvzkWG+hN1OV +N28mD41h75MfiSBRgwNIfpOKKYhsMyZAwkrytyHRPi3n3vcm2MYSrhnWktyF8Sml +MqsbGPUvXA7N7saAMKS9cjOk3KDHY105kbHw2h3bfIsF44p1z4aubzeAGjcEesQ1 +h7rQ9ps5eOJfGVInjb24vE0tDomvhs1hvOl2FvNJD7AciLHEuKGhiDhIQ4MKwGAI +s0YKpzhbvUzlsecaBDNiFdoC2txmSxWJ9VoTHWXkYMfTPq8R75VBLZ3W6GYVRtYp +egHwVBfbMaG+BLmCEbq5u/QIW4cziNBf0w36ewZV/0OJMlY63w7Lk/Y6+/NLh4Eg +YXZNeriuHThYW5GaYqPuKctEJRJpkD6FqleJNRLJQqu3hYrexV41izlrUgqREPRA +Oz3blA/9VJmzfAjDAKM5gWEVsvxBiOfnL73eGcNUHCw0mlmRaQfikHGuh7XkN6x0 +Iwg1BmcNFYdSM284GIo7T59nJwxL62NEF04ZHf8T5XfnUnwV0W08U+1sPiu4ZDFN +MMyzt1eVbqvGwc+AiUTXe1oV15kmn+9hMkOp0Re3Wz883tdIV54zNVMbYDeIGjze +vHtpIyKq3416s5BqPiXl+DoUa9YCrDQtWwIDAQABo1MwUTAdBgNVHQ4EFgQUxX9Q +BkRnBmdWaZ/0AhWSD80Wx78wHwYDVR0jBBgwFoAUxX9QBkRnBmdWaZ/0AhWSD80W +x78wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAouqM3ovXJrXI +RKK6AGX6jNUeD98VyF4+B3+XxfV/VLBOUsZCBEEUwQnoOPWAE78vVYAmy2dFnnCu +6x9mTSshCZT2LBK1RgTFkJkQbu4ME4EJvUuQ/plNdc4+9GpFBF/s+v1d4HupfcDH +UwuLnfFo0fW3NdH8a0WD3+HtlYsEV/6WLXZ6FuZJh8MGiZG/A9xfndu3qgt3Imaa +yNR9izbBjDANEs1sIXhpp2oFEm4JSE3WPuzVGsqA5Pb2yGsTw4dUtW8UW0CdIBcS +Itr6Do2gzsuEGFYB3UwsEvHbzUx23QLI6BHhUW1ozSUhkSWko5r3FzZ8kgpELUJ2 +iHisC2i3HTsUYgsaHMxiznc81vvrVsrWSrAFlKpt0KLLYUqARL2Wv1T5Q18WbFps +ol84F1Nyjh4Th2kAKDxoxqgWsPTYxkS72qFe16aa2C4dy4IZKIbSqW+NlkUXk/bI +SRM4UjIhkUoRsAB6+XfcI66t4DvazXti0Fwr7CcimwgZFpDmT9WhQcWezR4ASI6I +TMyx/2w4dPIjbstVTujCuzD/mQgTf3TPlCRHK2MzSSQtL5I3co4KOiiA4owEn5Ai +sXImV/RsvlQnZD+D03geBywkxiyZOLWYB4SzbsOu/wBoaPX2M+2jzbvkpy5Uryke +3oO6DfNAkt2YqYgX5lUd7A6SKQPZwCA= +-----END CERTIFICATE----- diff --git a/test/swarm_suite.cpp b/test/swarm_suite.cpp new file mode 100644 index 0000000..ae3ca48 --- /dev/null +++ b/test/swarm_suite.cpp @@ -0,0 +1,245 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016, Eugene Shalygin +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, 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/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/aux_/path.hpp" +#include +#include + +#include "test.hpp" +#include "test_utils.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%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 ": "" + , (flags & test_flags::v1_meta) ? "v1-meta ": "" + , (flags & test_flags::v2_meta) ? "v2-meta ": "" + ); + + // 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; + + pack.set_int(settings_pack::upload_rate_limit, int(rate_limit)); + pack.set_str(settings_pack::listen_interfaces, test_listen_interface()); + 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); + + pack.set_str(settings_pack::listen_interfaces, test_listen_interface()); + 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); + + pack.set_str(settings_pack::listen_interfaces, test_listen_interface()); + 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 v1 metadata using piece sizes smaller than 16kB + int const piece_size = (flags & test_flags::v1_meta) ? 8 * 1024 : 16 * 1024; + std::tie(tor1, tor2, tor3) = setup_transfer(&ses1, &ses2, &ses3, true + , false, true, "_swarm", piece_size, nullptr, bool(flags & test_flags::super_seeding), &p + , true, false, nullptr + , (flags & test_flags::v1_meta) ? create_torrent::v1_only + : (flags & test_flags::v2_meta) ? create_torrent::v2_only + : create_flags_t{}); + + if (flags & test_flags::time_critical) + { + tor2.set_piece_deadline(2_piece, 0); + tor2.set_piece_deadline(5_piece, 1000); + tor2.set_piece_deadline(8_piece, 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..695a183 --- /dev/null +++ b/test/swarm_suite.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2014, 2017-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, 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/flags.hpp" + +using test_flags_t = lt::flags::bitfield_flag; + +namespace test_flags +{ + using lt::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; + constexpr test_flags_t v1_meta = 6_bit; + constexpr test_flags_t v2_meta = 7_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..ea7d18c --- /dev/null +++ b/test/test.cpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016-2017, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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" + +namespace unit_test { + +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; + +namespace { +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; +} + +} // unit_test diff --git a/test/test.hpp b/test/test.hpp new file mode 100644 index 0000000..17e05fd --- /dev/null +++ b/test/test.hpp @@ -0,0 +1,197 @@ +/* + +Copyright (c) 2005, 2008-2010, 2013-2020, Arvid Norberg +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. + +*/ + +#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 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + +#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 + +namespace unit_test { + +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; + +} // unit_test + +#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) () { \ + auto& t = ::unit_test::g_unit_tests[::unit_test::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; \ + ::unit_test::g_num_unit_tests++; \ + } \ + } BOOST_PP_CAT(g_static_registrar_for, test_name); \ + static void BOOST_PP_CAT(unit_test_, test_name)() + +#define TEST_REPORT_AUX(x, line, file) \ + unit_test::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_add_torrent.cpp b/test/test_add_torrent.cpp new file mode 100644 index 0000000..f9ababf --- /dev/null +++ b/test/test_add_torrent.cpp @@ -0,0 +1,305 @@ +/* + +Copyright (c) 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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" // for load_file + +#include "libtorrent/flags.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/path.hpp" + +#include + +namespace { + +using add_torrent_test_flag_t = lt::flags::bitfield_flag; + +using lt::operator""_bit; + +#if TORRENT_ABI_VERSION < 3 +add_torrent_test_flag_t const set_info_hash = 0_bit; +#endif +add_torrent_test_flag_t const set_info_hashes_v1 = 1_bit; +add_torrent_test_flag_t const set_info_hashes_v2 = 2_bit; +add_torrent_test_flag_t const async_add = 3_bit; +add_torrent_test_flag_t const ec_add = 4_bit; +add_torrent_test_flag_t const magnet_link = 5_bit; +#if TORRENT_ABI_VERSION < 3 +add_torrent_test_flag_t const set_invalid_info_hash = 6_bit; +#endif +add_torrent_test_flag_t const set_invalid_info_hash_v1 = 7_bit; +add_torrent_test_flag_t const set_invalid_info_hash_v2 = 8_bit; + +lt::error_code test_add_torrent(std::string file, add_torrent_test_flag_t const flags) +{ + std::string const root_dir = lt::parent_path(lt::current_working_directory()); + std::string const filename = lt::combine_path(lt::combine_path(root_dir, "test_torrents"), file); + + lt::error_code ec; + std::vector data; + TEST_CHECK(load_file(filename, data, ec) == 0); + + auto ti = std::make_shared(data, ec, lt::from_span); + TEST_CHECK(!ec); + if (ec) std::printf(" loading(\"%s\") -> failed %s\n", filename.c_str() + , ec.message().c_str()); + + lt::add_torrent_params atp; + atp.ti = ti; + atp.save_path = "."; + +#if TORRENT_ABI_VERSION < 3 + if (flags & set_info_hash) atp.info_hash = atp.ti->info_hash(); +#endif + if (flags & set_info_hashes_v1) atp.info_hashes.v1 = atp.ti->info_hashes().v1; + if (flags & set_info_hashes_v2) atp.info_hashes.v2 = atp.ti->info_hashes().v2; +#if TORRENT_ABI_VERSION < 3 + if (flags & set_invalid_info_hash) atp.info_hash = lt::sha1_hash("abababababababababab"); +#endif + if (flags & set_invalid_info_hash_v1) atp.info_hashes.v1 = lt::sha1_hash("abababababababababab"); + if (flags & set_invalid_info_hash_v2) atp.info_hashes.v2 = lt::sha256_hash("abababababababababababababababab"); + + std::vector info_section; + + if (flags & magnet_link) + { + auto const is = atp.ti->info_section(); + info_section.assign(is.begin(), is.end()); + atp.ti.reset(); + } + + lt::session_params p; + p.settings.set_int(lt::settings_pack::alert_mask, lt::alert_category::error | lt::alert_category::status); + p.settings.set_str(lt::settings_pack::listen_interfaces, "127.0.0.1:6881"); + lt::session ses(p); + try + { + if (flags & ec_add) + { + ses.add_torrent(atp, ec); + if (ec) return ec; + } + else if (flags & async_add) + { + ses.async_add_torrent(atp); + } + else + { + ses.add_torrent(atp); + } + } + catch (lt::system_error const& e) + { + return e.code(); + } + + std::vector alerts; + auto const start_time = lt::clock_type::now(); + while (lt::clock_type::now() - start_time < lt::seconds(3)) + { + ses.wait_for_alert(lt::seconds(1)); + alerts.clear(); + ses.pop_alerts(&alerts); + for (auto const* a : alerts) + { + std::cout << a->message() << '\n'; + if (auto const* te = lt::alert_cast(a)) + { + return te->error; + } + + if (auto const* mf = lt::alert_cast(a)) + { + return mf->error; + } + + if (auto const* ta = lt::alert_cast(a)) + { + if (ta->error) return ta->error; + + if (flags & magnet_link) + { + // if this fails, we'll pick up the metadata_failed_alert + TEST_CHECK(ta->handle.is_valid()); + ta->handle.set_metadata(info_section); + } + else + { + // success! + return lt::error_code(); + } + } + + if (lt::alert_cast(a)) + { + // success! + return lt::error_code(); + } + } + } + + return lt::error_code(); +} + +struct test_case_t +{ + char const* filename; + add_torrent_test_flag_t flags; + lt::error_code expected_error; +}; + +auto const v2 = "v2.torrent"; +auto const hybrid = "v2_hybrid.torrent"; +auto const v1 = "base.torrent"; + +test_case_t const add_torrent_test_cases[] = { + {v2, {}, {}}, + {v2, set_info_hashes_v1, {}}, + {v2, set_info_hashes_v2, {}}, + {v2, set_info_hashes_v1 | set_info_hashes_v2, {}}, +#if TORRENT_ABI_VERSION < 3 + {v2, set_info_hash, {}}, + // the info_hash field is ignored when we have an actual torrent_info object + {v2, set_invalid_info_hash, {}}, +#endif + {v2, set_invalid_info_hash_v1, lt::errors::mismatching_info_hash}, + {v2, set_invalid_info_hash_v2, lt::errors::mismatching_info_hash}, + + {hybrid, {}, {}}, + {hybrid, set_info_hashes_v1, {}}, + {hybrid, set_info_hashes_v2, {}}, + {hybrid, set_info_hashes_v1 | set_info_hashes_v2, {}}, +#if TORRENT_ABI_VERSION < 3 + {hybrid, set_info_hash, {}}, + // the info_hash field is ignored when we have an actual torrent_info object + {hybrid, set_invalid_info_hash, {}}, +#endif + {hybrid, set_invalid_info_hash_v1, lt::errors::mismatching_info_hash}, + {hybrid, set_invalid_info_hash_v2, lt::errors::mismatching_info_hash}, + + {v1, {}, {}}, + {v1, set_info_hashes_v1, {}}, +#if TORRENT_ABI_VERSION < 3 + {v1, set_info_hash, {}}, + // the info_hash field is ignored when we have an actual torrent_info object + {v1, set_invalid_info_hash, {}}, +#endif + + // magnet links + {v2, magnet_link, lt::errors::missing_info_hash_in_uri}, + {v2, magnet_link | set_info_hashes_v1, {}}, + {v2, magnet_link | set_info_hashes_v2, {}}, +#if TORRENT_ABI_VERSION < 3 + // a v2-only magnet link supports magnet links with a truncated hash + {v2, magnet_link | set_info_hash, {}}, + {v2, magnet_link | set_invalid_info_hash, lt::errors::mismatching_info_hash}, +#endif + {v2, magnet_link | set_info_hashes_v1 | set_info_hashes_v2, {}}, + {v2, magnet_link | set_invalid_info_hash_v1, lt::errors::mismatching_info_hash}, + {v2, magnet_link | set_invalid_info_hash_v2, lt::errors::mismatching_info_hash}, + + {hybrid, magnet_link, lt::errors::missing_info_hash_in_uri}, + {hybrid, magnet_link | set_info_hashes_v1, {}}, + {hybrid, magnet_link | set_info_hashes_v2, {}}, +#if TORRENT_ABI_VERSION < 3 + {hybrid, magnet_link | set_info_hash, {}}, + {hybrid, magnet_link | set_invalid_info_hash, lt::errors::mismatching_info_hash}, +#endif + {hybrid, magnet_link | set_info_hashes_v1 | set_info_hashes_v2, {}}, + {hybrid, magnet_link | set_invalid_info_hash_v1, lt::errors::mismatching_info_hash}, + {hybrid, magnet_link | set_invalid_info_hash_v2, lt::errors::mismatching_info_hash}, + + {v1, magnet_link, lt::errors::missing_info_hash_in_uri}, +#if TORRENT_ABI_VERSION < 3 + {v1, magnet_link | set_info_hash, {}}, + {v1, magnet_link | set_invalid_info_hash, lt::errors::mismatching_info_hash}, +#endif + {v1, magnet_link | set_info_hashes_v1, {}}, + {v1, magnet_link | set_invalid_info_hash_v1, lt::errors::mismatching_info_hash}, + {v1, magnet_link | set_invalid_info_hash_v2, lt::errors::mismatching_info_hash}, +}; + +} + +TORRENT_TEST(invalid_file_root) +{ + TEST_CHECK(test_add_torrent("v2_invalid_root_hash.torrent", {}) == lt::error_code(lt::errors::torrent_invalid_piece_layer)); +} + +TORRENT_TEST(add_torrent) +{ + int i = 0; + for (auto const& test_case : add_torrent_test_cases) + { + std::cerr << "idx: " << i << '\n'; + auto const e = test_add_torrent(test_case.filename, test_case.flags); + if (e != test_case.expected_error) + { + std::cerr << test_case.filename << '\n'; + TEST_ERROR(e.message() + " != " + test_case.expected_error.message()); + } + ++i; + } +} + +TORRENT_TEST(async_add_torrent) +{ + int i = 0; + for (auto const& test_case : add_torrent_test_cases) + { + auto const e = test_add_torrent(test_case.filename, test_case.flags | async_add); + if (e != test_case.expected_error) + { + std::cerr << "idx: " << i << " " << test_case.filename << '\n'; + TEST_ERROR(e.message() + " != " + test_case.expected_error.message()); + } + ++i; + } +} + +TORRENT_TEST(ec_add_torrent) +{ + int i = 0; + for (auto const& test_case : add_torrent_test_cases) + { + auto const e = test_add_torrent(test_case.filename, test_case.flags | ec_add); + if (e != test_case.expected_error) + { + std::cerr << "idx: " << i << " " << test_case.filename << '\n'; + TEST_ERROR(e.message() + " != " + test_case.expected_error.message()); + } + ++i; + } +} diff --git a/test/test_alert_manager.cpp b/test/test_alert_manager.cpp new file mode 100644 index 0000000..0e9d5a9 --- /dev/null +++ b/test/test_alert_manager.cpp @@ -0,0 +1,364 @@ +/* + +Copyright (c) 2018, Steven Siloti +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2016, 2018, 2020, Alden Torres +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 "libtorrent/aux_/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) +{ + aux::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 (auto i = 0_piece; i < 600_piece; ++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 (auto i = 0_piece; i < 600_piece; ++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(); + aux::alert_manager mgr(inf, alert_category::all); + + TEST_EQUAL(mgr.alert_queue_size_limit(), inf); + + for (auto i = 0_piece; i < 600_piece; ++i) + mgr.emplace_alert(torrent_handle(), i); + + for (auto i = 0_piece; i < 600_piece; ++i) + mgr.emplace_alert(torrent_handle(), info_hash_t(), client_data_t{}); + + std::vector alerts; + mgr.get_all(alerts); + + TEST_EQUAL(alerts.size(), 1200); +} + +TORRENT_TEST(priority_limit) +{ + aux::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 (auto i = 0_piece; i < 200_piece; ++i) + mgr.emplace_alert(torrent_handle(), i); + + // the limit is twice as high for priority alerts + for (auto i = 0_file; i < 300_file; ++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; + aux::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)); + aux::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(aux::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) +{ + aux::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) +{ + aux::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) +{ + aux::alert_manager mgr(100, alert_category::all); + std::vector alerts(10); + + mgr.get_all(alerts); + + TEST_CHECK(alerts.empty()); +} + +TORRENT_TEST(dropped_alerts) +{ + aux::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) +{ + aux::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); + +#ifndef TORRENT_DISABLE_ALERT_MSG + TEST_EQUAL(alerts.back()->message(), "dropped alerts: torrent_finished "); +#endif + auto* a = lt::alert_cast(alerts.back()); + TEST_CHECK(a); + TEST_CHECK(a->dropped_alerts[torrent_finished_alert::alert_type] == true); +} + +#ifndef TORRENT_DISABLE_EXTENSIONS +struct post_plugin : lt::plugin +{ + explicit post_plugin(aux::alert_manager& m) : mgr(m) {} + void on_alert(alert const*) override + { + if (++depth > 10) return; + mgr.emplace_alert(torrent_handle(), 0_piece); + } + + aux::alert_manager& mgr; + int depth = 0; +}; + +// make sure the alert manager supports alerts being posted while executing a +// plugin handler +TORRENT_TEST(recursive_alerts) +{ + aux::alert_manager mgr(100, alert_category::all); + auto pl = std::make_shared(mgr); + mgr.add_extension(pl); + + mgr.emplace_alert(torrent_handle(), 0_piece); + + 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..5678752 --- /dev/null +++ b/test/test_alert_types.cpp @@ -0,0 +1,355 @@ +/* + +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2017, 2020, Alden Torres +Copyright (c) 2020, Fonic +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_/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_CHECK(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, alert_priority::normal, 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, alert_priority::critical, alert_category::status); + TEST_ALERT_TYPE(read_piece_alert, 5, alert_priority::critical, alert_category::storage); + TEST_ALERT_TYPE(file_completed_alert, 6, alert_priority::normal, PROGRESS_NOTIFICATION alert_category::file_progress); + TEST_ALERT_TYPE(file_renamed_alert, 7, alert_priority::critical, alert_category::storage); + TEST_ALERT_TYPE(file_rename_failed_alert, 8, alert_priority::critical, alert_category::storage); + TEST_ALERT_TYPE(performance_alert, 9, alert_priority::normal, alert::performance_warning); + TEST_ALERT_TYPE(state_changed_alert, 10, alert_priority::high, alert_category::status); + TEST_ALERT_TYPE(tracker_error_alert, 11, alert_priority::high, alert_category::tracker | alert_category::error); + TEST_ALERT_TYPE(tracker_warning_alert, 12, alert_priority::normal, alert_category::tracker | alert_category::error); + TEST_ALERT_TYPE(scrape_reply_alert, 13, alert_priority::critical, alert_category::tracker); + TEST_ALERT_TYPE(scrape_failed_alert, 14, alert_priority::critical, alert_category::tracker | alert_category::error); + TEST_ALERT_TYPE(tracker_reply_alert, 15, alert_priority::normal, alert_category::tracker); + TEST_ALERT_TYPE(dht_reply_alert, 16, alert_priority::normal, alert_category::dht | alert_category::tracker); + TEST_ALERT_TYPE(tracker_announce_alert, 17, alert_priority::normal, alert_category::tracker); + TEST_ALERT_TYPE(hash_failed_alert, 18, alert_priority::normal, alert_category::status); + TEST_ALERT_TYPE(peer_ban_alert, 19, alert_priority::normal, alert_category::peer); + TEST_ALERT_TYPE(peer_unsnubbed_alert, 20, alert_priority::normal, alert_category::peer); + TEST_ALERT_TYPE(peer_snubbed_alert, 21, alert_priority::normal, alert_category::peer); + TEST_ALERT_TYPE(peer_error_alert, 22, alert_priority::normal, alert_category::peer); + TEST_ALERT_TYPE(peer_connect_alert, 23, alert_priority::normal, alert_category::connect); + TEST_ALERT_TYPE(peer_disconnected_alert, 24, alert_priority::normal, alert_category::connect); + TEST_ALERT_TYPE(invalid_request_alert, 25, alert_priority::normal, alert_category::peer); + TEST_ALERT_TYPE(torrent_finished_alert, 26, alert_priority::high, alert_category::status); + TEST_ALERT_TYPE(piece_finished_alert, 27, alert_priority::normal, PROGRESS_NOTIFICATION alert_category::piece_progress); + TEST_ALERT_TYPE(request_dropped_alert, 28, alert_priority::normal, PROGRESS_NOTIFICATION alert_category::block_progress | alert_category::peer); + TEST_ALERT_TYPE(block_timeout_alert, 29, alert_priority::normal, PROGRESS_NOTIFICATION alert_category::block_progress | alert_category::peer); + TEST_ALERT_TYPE(block_finished_alert, 30, alert_priority::normal, PROGRESS_NOTIFICATION alert_category::block_progress); + TEST_ALERT_TYPE(block_downloading_alert, 31, alert_priority::normal, PROGRESS_NOTIFICATION alert_category::block_progress); + TEST_ALERT_TYPE(unwanted_block_alert, 32, alert_priority::normal, alert_category::peer); + TEST_ALERT_TYPE(storage_moved_alert, 33, alert_priority::critical, alert_category::storage); + TEST_ALERT_TYPE(storage_moved_failed_alert, 34, alert_priority::critical, alert_category::storage); + TEST_ALERT_TYPE(torrent_deleted_alert, 35, alert_priority::critical, alert_category::storage); + TEST_ALERT_TYPE(torrent_delete_failed_alert, 36, alert_priority::critical, alert_category::storage | alert_category::error); + TEST_ALERT_TYPE(save_resume_data_alert, 37, alert_priority::critical, alert_category::storage); + TEST_ALERT_TYPE(save_resume_data_failed_alert, 38, alert_priority::critical, alert_category::storage | alert_category::error); + TEST_ALERT_TYPE(torrent_paused_alert, 39, alert_priority::high, alert_category::status); + TEST_ALERT_TYPE(torrent_resumed_alert, 40, alert_priority::high, alert_category::status); + TEST_ALERT_TYPE(torrent_checked_alert, 41, alert_priority::high, alert_category::status); + TEST_ALERT_TYPE(url_seed_alert, 42, alert_priority::normal, alert_category::peer | alert_category::error); + TEST_ALERT_TYPE(file_error_alert, 43, alert_priority::high, alert_category::status | alert_category::error | alert_category::storage); + TEST_ALERT_TYPE(metadata_failed_alert, 44, alert_priority::normal, alert_category::error); + TEST_ALERT_TYPE(metadata_received_alert, 45, alert_priority::normal, alert_category::status); + TEST_ALERT_TYPE(udp_error_alert, 46, alert_priority::normal, alert_category::error); + TEST_ALERT_TYPE(external_ip_alert, 47, alert_priority::normal, alert_category::status); + TEST_ALERT_TYPE(listen_failed_alert, 48, alert_priority::critical, alert_category::status | alert_category::error); + TEST_ALERT_TYPE(listen_succeeded_alert, 49, alert_priority::critical, alert_category::status); + TEST_ALERT_TYPE(portmap_error_alert, 50, alert_priority::normal, alert_category::port_mapping | alert_category::error); + TEST_ALERT_TYPE(portmap_alert, 51, alert_priority::normal, alert_category::port_mapping); + TEST_ALERT_TYPE(portmap_log_alert, 52, alert_priority::normal, alert_category::port_mapping_log); + TEST_ALERT_TYPE(fastresume_rejected_alert, 53, alert_priority::critical, alert_category::status | alert_category::error); + TEST_ALERT_TYPE(peer_blocked_alert, 54, alert_priority::normal, alert_category::ip_block); + TEST_ALERT_TYPE(dht_announce_alert, 55, alert_priority::normal, alert_category::dht); + TEST_ALERT_TYPE(dht_get_peers_alert, 56, alert_priority::normal, alert_category::dht); +#if TORRENT_ABI_VERSION <= 2 + TEST_ALERT_TYPE(stats_alert, 57, alert_priority::normal, alert_category::stats); +#else + count_alert_types++; +#endif + TEST_ALERT_TYPE(cache_flushed_alert, 58, alert_priority::high, alert_category::storage); +#if TORRENT_ABI_VERSION == 1 + TEST_ALERT_TYPE(anonymous_mode_alert, 59, alert_priority::normal, alert_category::error); +#else + count_alert_types++; +#endif + TEST_ALERT_TYPE(lsd_peer_alert, 60, alert_priority::normal, alert_category::peer); + TEST_ALERT_TYPE(trackerid_alert, 61, alert_priority::normal, alert_category::status); + TEST_ALERT_TYPE(dht_bootstrap_alert, 62, alert_priority::normal, alert_category::dht); + count_alert_types++; // 63 is gone + TEST_ALERT_TYPE(torrent_error_alert, 64, alert_priority::high, alert_category::error | alert_category::status); + TEST_ALERT_TYPE(torrent_need_cert_alert, 65, alert_priority::critical, alert_category::status); + TEST_ALERT_TYPE(incoming_connection_alert, 66, alert_priority::normal, alert_category::peer); + TEST_ALERT_TYPE(add_torrent_alert, 67, alert_priority::critical, alert_category::status); + TEST_ALERT_TYPE(state_update_alert, 68, alert_priority::high, alert_category::status); +#if TORRENT_ABI_VERSION == 1 + TEST_ALERT_TYPE(mmap_cache_alert, 69, alert_priority::normal, alert_category::error); +#else + count_alert_types++; +#endif + TEST_ALERT_TYPE(session_stats_alert, 70, alert_priority::critical, alert_category_t{}); + count_alert_types++; + count_alert_types++; // 72 is gone + TEST_ALERT_TYPE(dht_error_alert, 73, alert_priority::normal, alert_category::error | alert_category::dht); + TEST_ALERT_TYPE(dht_immutable_item_alert, 74, alert_priority::critical, alert_category::dht); + TEST_ALERT_TYPE(dht_mutable_item_alert, 75, alert_priority::critical, alert_category::dht); + TEST_ALERT_TYPE(dht_put_alert, 76, alert_priority::normal, alert_category::dht); + TEST_ALERT_TYPE(i2p_alert, 77, alert_priority::normal, alert_category::error); + TEST_ALERT_TYPE(dht_outgoing_get_peers_alert, 78, alert_priority::normal, alert_category::dht); + TEST_ALERT_TYPE(log_alert, 79, alert_priority::normal, alert_category::session_log); + TEST_ALERT_TYPE(torrent_log_alert, 80, alert_priority::normal, alert_category::torrent_log); + TEST_ALERT_TYPE(peer_log_alert, 81, alert_priority::normal, alert_category::peer_log); + TEST_ALERT_TYPE(lsd_error_alert, 82, alert_priority::normal, alert_category::error); + TEST_ALERT_TYPE(dht_stats_alert, 83, alert_priority::normal, alert_category_t{}); + TEST_ALERT_TYPE(incoming_request_alert, 84, alert_priority::normal, alert_category::incoming_request); + TEST_ALERT_TYPE(dht_log_alert, 85, alert_priority::normal, alert_category::dht_log); + TEST_ALERT_TYPE(dht_pkt_alert, 86, alert_priority::normal, alert_category::dht_log); + TEST_ALERT_TYPE(dht_get_peers_reply_alert, 87, alert_priority::normal, alert_category::dht_operation); + TEST_ALERT_TYPE(dht_direct_response_alert, 88, alert_priority::critical, alert_category::dht); + TEST_ALERT_TYPE(picker_log_alert, 89, alert_priority::normal, alert_category::picker_log); + TEST_ALERT_TYPE(session_error_alert, 90, alert_priority::normal, alert_category::error); + TEST_ALERT_TYPE(dht_live_nodes_alert, 91, alert_priority::normal, alert_category::dht); + TEST_ALERT_TYPE(session_stats_header_alert, 92, alert_priority::normal, alert_category_t{}); + TEST_ALERT_TYPE(dht_sample_infohashes_alert, 93, alert_priority::normal, alert_category::dht_operation); + TEST_ALERT_TYPE(block_uploaded_alert, 94, alert_priority::normal, PROGRESS_NOTIFICATION alert_category::upload); + TEST_ALERT_TYPE(alerts_dropped_alert, 95, alert_priority::meta, alert_category::error); + TEST_ALERT_TYPE(socks5_alert, 96, alert_priority::normal, alert_category::error); + TEST_ALERT_TYPE(file_prio_alert, 97, alert_priority::normal, alert_category::storage); + TEST_ALERT_TYPE(oversized_file_alert, 98, alert_priority::normal, alert_category::storage); + +#undef TEST_ALERT_TYPE + + TEST_EQUAL(num_alert_types, 99); + TEST_EQUAL(num_alert_types, count_alert_types); +} + +TORRENT_TEST(dht_get_peers_reply_alert) +{ + aux::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) +{ + aux::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) +{ + aux::alert_manager mgr(1, {}); + + 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); +#ifndef TORRENT_DISABLE_ALERT_MSG + TEST_CHECK(h->message().find("session stats header: ") != std::string::npos); +#endif + + auto const* v = alert_cast(alerts[1]); + TEST_CHECK(v != nullptr); +#ifndef TORRENT_DISABLE_ALERT_MSG + TEST_CHECK(v->message().find("session stats (") != std::string::npos); +#endif +} + +TORRENT_TEST(dht_sample_infohashes_alert) +{ + aux::alert_manager mgr(1, dht_sample_infohashes_alert::static_category); + + TEST_EQUAL(mgr.should_post(), true); + + sha1_hash const node_id = rand_hash(); + 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(node_id, endpoint, interval, num, v, nv); + + auto const* a = alert_cast(mgr.wait_for_alert(seconds(0))); + TEST_CHECK(a != nullptr); + + TEST_EQUAL(a->node_id, node_id); + 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); +} + +#ifndef TORRENT_DISABLE_ALERT_MSG +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); +} +#endif + +#undef PROGRESS_NOTIFICATION diff --git a/test/test_alloca.cpp b/test/test_alloca.cpp new file mode 100644 index 0000000..a8413a4 --- /dev/null +++ b/test/test_alloca.cpp @@ -0,0 +1,82 @@ +/* + +Copyright (c) 2017, 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 "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); +} + +TORRENT_TEST(alloca_large) +{ + // this is something like 256 kiB of allocation + // it should be made on the heap and always succeed + TORRENT_ALLOCA(vec, A, 65536); + for (auto const& a : vec) + TEST_EQUAL(a.val, 1337); +} + diff --git a/test/test_apply_pad.cpp b/test/test_apply_pad.cpp new file mode 100644 index 0000000..55c78a9 --- /dev/null +++ b/test/test_apply_pad.cpp @@ -0,0 +1,175 @@ +/* + +Copyright (c) 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 "libtorrent/aux_/apply_pad_files.hpp" + +using namespace lt; + +namespace { + +struct piece_byte +{ + piece_index_t piece; + std::int64_t bytes; +}; + +struct expect_calls +{ + expect_calls(std::vector const& calls) + : m_calls(calls) + {} + + void operator()(piece_index_t const piece, int const bytes) + { + TEST_CHECK(!m_calls.empty()); + if (m_calls.empty()) return; + auto const expected = m_calls.front(); + m_calls.erase(m_calls.begin()); + TEST_EQUAL(piece, expected.piece); + TEST_EQUAL(bytes, expected.bytes); + m_total += bytes; + } + + ~expect_calls() + { + TEST_CHECK(m_calls.empty()); + } + + std::int64_t total_pad() const { return m_total; } + +private: + std::int64_t m_total = 0; + std::vector m_calls; +}; + +} + +TORRENT_TEST(simple) +{ + auto const fs = make_files({{0x3ff0, false}, {0x10, true}}, 0x4000); + expect_calls c({{0_piece, 0x10}}); + aux::apply_pad_files(fs, c); + TEST_EQUAL(c.total_pad(), 0x10); +} + +TORRENT_TEST(irregular_last_piece) +{ + auto const fs = make_files({{0x3ff0, false}, {0x20, true}}, 0x4000); + expect_calls c({{1_piece, 0x10}, {0_piece, 0x10}}); + aux::apply_pad_files(fs, c); + TEST_EQUAL(c.total_pad(), 0x20); +} + +TORRENT_TEST(full_piece) +{ + auto const fs = make_files({{0x4000, false}, {0x4000, true}}, 0x4000); + expect_calls c({{1_piece, 0x4000}}); + aux::apply_pad_files(fs, c); + TEST_EQUAL(c.total_pad(), 0x4000); +} + +TORRENT_TEST(1_byte_pad) +{ + auto const fs = make_files({{0x3fff, false}, {0x1, true}}, 0x4000); + expect_calls c({{0_piece, 0x1}}); + aux::apply_pad_files(fs, c); + TEST_EQUAL(c.total_pad(), 0x1); +} + +TORRENT_TEST(span_multiple_pieces) +{ + auto const fs = make_files({{0x8001, false}, {0x7fff, true}}, 0x4000); + expect_calls c({{3_piece, 0x4000}, {2_piece, 0x3fff}}); + aux::apply_pad_files(fs, c); + TEST_EQUAL(c.total_pad(), 0x7fff); +} + +TORRENT_TEST(span_multiple_full_pieces) +{ + auto const fs = make_files({{0x8000, false}, {0x8000, true}}, 0x4000); + expect_calls c({{3_piece, 0x4000}, {2_piece, 0x4000}}); + aux::apply_pad_files(fs, c); + TEST_EQUAL(c.total_pad(), 0x8000); +} + +TORRENT_TEST(small_pieces) +{ + auto const fs = make_files({{0x2001, false}, {0x1fff, true}}, 0x1000); + expect_calls c({{3_piece, 0x1000}, {2_piece, 0xfff}}); + aux::apply_pad_files(fs, c); + TEST_EQUAL(c.total_pad(), 0x1fff); +} + +TORRENT_TEST(smalll_piece_1_byte_pad) +{ + auto const fs = make_files({{0xfff, false}, {0x1, true}}, 0x1000); + expect_calls c({{0_piece, 0x1}}); + aux::apply_pad_files(fs, c); + TEST_EQUAL(c.total_pad(), 0x1); +} + +TORRENT_TEST(back_to_back_pads) +{ + // In this scenario, the first pad file is invalid. It doesn't align the + // next file to a piece boundary, nor is it the last file. It will be + // treated like a normal file by the piece picker + auto const fs = make_files({{0x3ff0, false}, {0x8, true}, {0x8, true}}, 0x4000); + expect_calls c({{0_piece, 0x8}}); + aux::apply_pad_files(fs, c); + TEST_EQUAL(c.total_pad(), 0x8); +} + +TORRENT_TEST(large_pad_file) +{ + auto const fs = make_files({{0x4001, false}, {0x100003fff, true}}, 0x4000); + piece_index_t expected_piece(fs.num_pieces() - 1); + int num_calls = 0; + aux::apply_pad_files(fs, [&](piece_index_t const piece, int const bytes) + { + TEST_EQUAL(piece, expected_piece); + if (piece == 1_piece) + { + TEST_EQUAL(bytes, 0x3fff); + } + else + { + TEST_EQUAL(bytes, 0x4000); + --expected_piece; + } + ++num_calls; + }); + TEST_EQUAL(num_calls, 262145); + TEST_EQUAL(expected_piece, 1_piece); +} + diff --git a/test/test_auto_unchoke.cpp b/test/test_auto_unchoke.cpp new file mode 100644 index 0000000..577fe21 --- /dev/null +++ b/test/test_auto_unchoke.cpp @@ -0,0 +1,165 @@ +/* + +Copyright (c) 2008-2009, 2012-2019, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2018, 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/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/aux_/path.hpp" +#include +#include + +#include "test.hpp" +#include "test_utils.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, test_listen_interface()); + 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, test_listen_interface()); + + lt::session ses2(pack); + + pack.set_str(settings_pack::listen_interfaces, test_listen_interface()); + + 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..4205cf1 --- /dev/null +++ b/test/test_bandwidth_limiter.cpp @@ -0,0 +1,519 @@ +/* + +Copyright (c) 2007-2009, 2014-2020, Arvid Norberg +Copyright (c) 2016-2017, Andrei Kurushin +Copyright (c) 2016-2018, 2020, 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/aux_/bandwidth_manager.hpp" +#include "libtorrent/aux_/bandwidth_queue_entry.hpp" +#include "libtorrent/aux_/bandwidth_limit.hpp" +#include "libtorrent/aux_/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 + +aux::bandwidth_channel global_bwc; + +struct peer_connection: aux::bandwidth_socket, std::enable_shared_from_this +{ + peer_connection(aux::bandwidth_manager& bwm + , aux::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(); + + aux::bandwidth_manager& m_bwm; + + aux::bandwidth_channel m_bandwidth_channel; + aux::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() +{ + aux::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(aux::bandwidth_channel& t1, aux::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 + , aux::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, aux::bandwidth_manager& bwm + , aux::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; + aux::bandwidth_manager manager(0); + global_bwc.throttle(limit); + + aux::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; + aux::bandwidth_manager manager(0); + global_bwc.throttle(0); + + aux::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; + aux::bandwidth_manager manager(0); + aux::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; + aux::bandwidth_manager manager(0); + global_bwc.throttle(global_limit); + + aux::bandwidth_channel t1; + aux::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; + aux::bandwidth_manager manager(0); + global_bwc.throttle(global_limit); + + aux::bandwidth_channel t1; + aux::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; + aux::bandwidth_manager manager(0); + aux::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; + aux::bandwidth_manager manager(0); + aux::bandwidth_channel t1; + aux::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..a6df542 --- /dev/null +++ b/test/test_bdecode.cpp @@ -0,0 +1,1341 @@ +/* + +Copyright (c) 2015-2019, Arvid Norberg +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 "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); +} + +TORRENT_TEST(data_offset) +{ + char const 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.data_offset(), 0); + TEST_EQUAL(e.list_at(0).data_offset(), 1); + TEST_EQUAL(e.list_at(1).data_offset(), 4); + TEST_EQUAL(e.list_at(2).data_offset(), 9); + TEST_EQUAL(e.list_at(3).data_offset(), 17); +} + +TORRENT_TEST(string_offset) +{ + char const b[] = "l3:foo3:bare"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!ec); + std::printf("%s\n", print_entry(e).c_str()); + + TEST_EQUAL(e.list_at(0).string_offset(), 3); + TEST_EQUAL(e.list_at(1).string_offset(), 8); +} + +TORRENT_TEST(dict_at_node) +{ + char const b[] = "d3:foo3:bar4:test4:teste"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!ec); + std::printf("%s\n", print_entry(e).c_str()); + + TEST_EQUAL(e.dict_at_node(0).first.string_offset(), 3); + TEST_EQUAL(e.dict_at_node(0).second.string_offset(), 8); + TEST_EQUAL(e.dict_at_node(1).first.string_offset(), 13); + TEST_EQUAL(e.dict_at_node(1).second.string_offset(), 19); +} diff --git a/test/test_bencoding.cpp b/test/test_bencoding.cpp new file mode 100644 index 0000000..f6d4225 --- /dev/null +++ b/test/test_bencoding.cpp @@ -0,0 +1,274 @@ +/* + +Copyright (c) 2005, 2008, 2015-2020, Arvid Norberg +Copyright (c) 2018, Eugene Shalygin +Copyright (c) 2018, 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/bencode.hpp" +#include "libtorrent/bdecode.hpp" + +#include +#include +#include + +#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::aux::integer_to_str; + + std::array buf; + TEST_CHECK(integer_to_str(buf, 0) == "0"_sv); + TEST_CHECK(integer_to_str(buf, 1) == "1"_sv); + TEST_CHECK(integer_to_str(buf, 2) == "2"_sv); + TEST_CHECK(integer_to_str(buf, 3) == "3"_sv); + TEST_CHECK(integer_to_str(buf, 4) == "4"_sv); + TEST_CHECK(integer_to_str(buf, 5) == "5"_sv); + TEST_CHECK(integer_to_str(buf, 6) == "6"_sv); + TEST_CHECK(integer_to_str(buf, 7) == "7"_sv); + TEST_CHECK(integer_to_str(buf, 8) == "8"_sv); + TEST_CHECK(integer_to_str(buf, 9) == "9"_sv); + TEST_CHECK(integer_to_str(buf, 10) == "10"_sv); + TEST_CHECK(integer_to_str(buf, 11) == "11"_sv); + TEST_CHECK(integer_to_str(buf, -1) == "-1"_sv); + TEST_CHECK(integer_to_str(buf, -2) == "-2"_sv); + TEST_CHECK(integer_to_str(buf, -3) == "-3"_sv); + TEST_CHECK(integer_to_str(buf, -4) == "-4"_sv); + TEST_CHECK(integer_to_str(buf, -5) == "-5"_sv); + TEST_CHECK(integer_to_str(buf, -6) == "-6"_sv); + TEST_CHECK(integer_to_str(buf, -7) == "-7"_sv); + TEST_CHECK(integer_to_str(buf, -8) == "-8"_sv); + TEST_CHECK(integer_to_str(buf, -9) == "-9"_sv); + TEST_CHECK(integer_to_str(buf, -10) == "-10"_sv); + TEST_CHECK(integer_to_str(buf, -11) == "-11"_sv); + TEST_CHECK(integer_to_str(buf, 12) == "12"_sv); + TEST_CHECK(integer_to_str(buf, -12) == "-12"_sv); + TEST_CHECK(integer_to_str(buf, 123) == "123"_sv); + TEST_CHECK(integer_to_str(buf, -123) == "-123"_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, 12345) == "12345"_sv); + TEST_CHECK(integer_to_str(buf, -12345) == "-12345"_sv); + TEST_CHECK(integer_to_str(buf, 123456) == "123456"_sv); + TEST_CHECK(integer_to_str(buf, -123456) == "-123456"_sv); + TEST_CHECK(integer_to_str(buf, 123456789012345678LL) == "123456789012345678"_sv); + TEST_CHECK(integer_to_str(buf, -123456789012345678LL) == "-123456789012345678"_sv); + TEST_CHECK(integer_to_str(buf, std::numeric_limits::max()) == "9223372036854775807"_sv); + TEST_CHECK(integer_to_str(buf, std::numeric_limits::min()) == "-9223372036854775808"_sv); +} diff --git a/test/test_bitfield.cpp b/test/test_bitfield.cpp new file mode 100644 index 0000000..186c621 --- /dev/null +++ b/test/test_bitfield.cpp @@ -0,0 +1,431 @@ +/* + +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Falcosc +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_bloom_filter.cpp b/test/test_bloom_filter.cpp new file mode 100644 index 0000000..4fccb8e --- /dev/null +++ b/test/test_bloom_filter.cpp @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2015-2017, 2019, Arvid Norberg +Copyright (c) 2018, 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/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..c19cf71 --- /dev/null +++ b/test/test_buffer.cpp @@ -0,0 +1,311 @@ +/* +Copyright (c) 2005, 2007-2008, 2014-2019, Arvid Norberg +Copyright (c) 2018, 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 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/aux_/buffer.hpp" +#include "libtorrent/aux_/chained_buffer.hpp" +#include "libtorrent/socket.hpp" + +#include "test.hpp" + +using namespace lt; +using lt::aux::buffer; +using lt::aux::chained_buffer; + +// -- 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 (auto const& i : b) + { + memcpy(target, i.data(), i.size()); + target += i.size(); + copied += int(i.size()); + } + 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..3f7ff51 --- /dev/null +++ b/test/test_checking.cpp @@ -0,0 +1,435 @@ +/* + +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, d-komarov +Copyright (c) 2018, 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 // for chmod + +#include "libtorrent/session.hpp" +#include "libtorrent/session_params.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" +#include "libtorrent/aux_/open_mode.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, + + v2 = 32, + + single_file = 64, +}; + +void test_checking(int const flags) +{ + using namespace lt; + + std::printf("\n==== TEST CHECKING %s%s%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 ":"" + , (flags & v2) ? "v2 ":"" + , (flags & single_file) ? "single_file ":""); + + 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; + int const piece_size = (flags & single_file) ? 0x8000 : 0x4000; + + auto const file_sizes = (flags & single_file) + ? std::vector{500000} + : std::vector{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,50000,73000,900,43000,400,4300,6, 4 }; + + create_random_files("test_torrent_dir", file_sizes, &fs); + + lt::create_torrent t(fs, piece_size, (flags & v2) ? create_torrent::v2_only : create_torrent::v1_only); + + // 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); + TEST_CHECK(ti->is_valid()); + + std::printf("generated torrent: %s test_torrent_dir\n" + , aux::to_hex(ti->info_hashes().v1).c_str()); + + // truncate every file in half + if (flags & (incomplete_files | extended_files)) + { + for (std::size_t i = 0; i < file_sizes.size(); ++i) + { + if ((i & 1) == 1) continue; + 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::int64_t const new_len = (flags & extended_files) + ? file_sizes[i] + 10 + : file_sizes[i] * 2 / 3; + + int const ret = ::truncate(path.c_str(), new_len); + if (ret < 0) + { + std::printf("ERROR: truncating file \"%s\": (%d) %s\n" + , path.c_str(), errno, strerror(errno)); + } + } + } + + // 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 + std::vector 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", (flags & single_file) ? file_sizes : 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 + lt::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 | corrupt_files)) + { + TEST_CHECK(!st.is_seeding); + + std::this_thread::sleep_for(lt::milliseconds(500)); + st = tor1.status(); + + TEST_CHECK(!st.errc); + if (st.errc) + std::printf("error: %s\n", st.errc.message().c_str()); + std::vector const file_progress = tor1.file_progress(); + bool one_incomplete = false; + file_storage const& fs1 = ti->files(); + for (file_index_t i : fs1.file_range()) + { + if (fs1.pad_file_at(i)) continue; + std::printf("file: %d progress: %" PRId64 " / %" PRId64 "\n", static_cast(i) + , file_progress[std::size_t(static_cast(i))], fs1.file_size(i)); + if (fs1.file_size(i) == file_progress[std::size_t(static_cast(i))]) continue; + one_incomplete = true; + } + TEST_CHECK(one_incomplete); + TEST_CHECK(st.num_pieces < ti->num_pieces()); + } + else + { + TEST_CHECK(st.num_pieces == ti->num_pieces()); + 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(checking_v2) +{ + test_checking(v2); +} + +TORRENT_TEST(read_only_corrupt_v2) +{ + test_checking(read_only_files | corrupt_files | v2); +} + +TORRENT_TEST(read_only_v2) +{ + test_checking(read_only_files | v2); +} + +TORRENT_TEST(incomplete_v2) +{ + test_checking(incomplete_files | v2); +} + +TORRENT_TEST(corrupt_v2) +{ + test_checking(corrupt_files | v2); +} + +TORRENT_TEST(single_file_v2) +{ + test_checking(v2 | single_file); +} + +TORRENT_TEST(single_file_corrupt_v2) +{ + test_checking(corrupt_files | v2 | single_file); +} + +TORRENT_TEST(single_file_incomplete_v2) +{ + test_checking(incomplete_files | v2 | single_file); +} + +TORRENT_TEST(force_recheck_v2) +{ + test_checking(force_recheck | v2); +} + +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); + 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 result: %s\n" + , aux::to_hex(ti->info_hashes().v1.to_string()).c_str() + , ec.message().c_str()); + + TEST_CHECK(ti->is_valid()); + + // we have two files, but there are also two padfiles now + 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_copy_file.cpp b/test/test_copy_file.cpp new file mode 100644 index 0000000..6b403a4 --- /dev/null +++ b/test/test_copy_file.cpp @@ -0,0 +1,232 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_/path.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/mmap.hpp" +#include "libtorrent/aux_/open_mode.hpp" +#include "test.hpp" + +#include +#include + +#ifndef TORRENT_WINDOWS +#include +#endif + +#ifdef TORRENT_LINUX +#include +#include +#endif + +namespace { + +void write_file(std::string const& filename, int size) +{ + std::vector v; + v.resize(std::size_t(size)); + for (int i = 0; i < size; ++i) + v[std::size_t(i)] = char(i & 255); + + std::ofstream(filename.c_str()).write(v.data(), std::streamsize(v.size())); +} + +bool compare_files(std::string const& file1, std::string const& file2) +{ + lt::error_code ec; + lt::file_status st1; + lt::file_status st2; + lt::stat_file(file1, &st1, ec); + TEST_CHECK(!ec); + lt::stat_file(file2, &st2, ec); + TEST_CHECK(!ec); + if (st1.file_size != st2.file_size) + return false; + + std::ifstream f1(file1.c_str()); + std::ifstream f2(file2.c_str()); + using it = std::istream_iterator; + return std::equal(it(f1), it{}, it(f2)); +} + +#if defined TORRENT_WINDOWS +bool fs_supports_sparse_files() +{ +#ifdef TORRENT_WINRT + HANDLE test = ::CreateFile2(L"test" + , GENERIC_WRITE + , FILE_SHARE_READ + , OPEN_ALWAYS + , nullptr); +#else + HANDLE test = ::CreateFileA("test" + , GENERIC_WRITE + , FILE_SHARE_READ + , nullptr + , OPEN_ALWAYS + , FILE_FLAG_SEQUENTIAL_SCAN + , nullptr); +#endif + TEST_CHECK(test != INVALID_HANDLE_VALUE); + DWORD fs_flags = 0; + wchar_t fs_name[50]; + TEST_CHECK(::GetVolumeInformationByHandleW(test, nullptr, 0, nullptr, nullptr + , &fs_flags, fs_name, sizeof(fs_name)) != 0); + ::CloseHandle(test); + printf("filesystem: %S\n", fs_name); + return (fs_flags & FILE_SUPPORTS_SPARSE_FILES) != 0; +} + +#else + +bool fs_supports_sparse_files() +{ + int test = ::open("test", O_RDWR | O_CREAT, 0755); + TEST_CHECK(test >= 0); + struct statfs st{}; + TEST_CHECK(fstatfs(test, &st) == 0); + ::close(test); +#ifdef TORRENT_LINUX + static long const ufs = 0x00011954; + static const std::set sparse_filesystems{ + EXT4_SUPER_MAGIC, EXT3_SUPER_MAGIC, XFS_SUPER_MAGIC, BTRFS_SUPER_MAGIC + , ufs, REISERFS_SUPER_MAGIC + }; + printf("filesystem: %ld\n", st.f_type); + return sparse_filesystems.count(st.f_type); +#else + printf("filesystem: (%d) %s\n", st.f_type, st.f_fstypename); + static const std::set sparse_filesystems{ + "ufs", "zfs", "ext4", "xfs", "apfs", "btrfs"}; + return sparse_filesystems.count(st.f_fstypename); +#endif +} + +#endif +} + +TORRENT_TEST(basic) +{ + write_file("basic-1", 10); + lt::error_code ec; + lt::copy_file("basic-1", "basic-1.copy", ec); + TEST_CHECK(!ec); + TEST_CHECK(compare_files("basic-1", "basic-1.copy")); + + write_file("basic-2", 1000000); + lt::copy_file("basic-2", "basic-2.copy", ec); + TEST_CHECK(!ec); + TEST_CHECK(compare_files("basic-2", "basic-2.copy")); +} + +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE +TORRENT_TEST(sparse_file) +{ + using lt::aux::file_handle; + using lt::aux::file_mapping; + using lt::aux::file_view; + namespace open_mode = lt::aux::open_mode; + + { + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + auto open_unmap_lock = std::make_shared(); +#endif + file_handle f("sparse-1", 50'000'000 + , open_mode::write | open_mode::truncate | open_mode::sparse); + auto map = std::make_shared(std::move(f), lt::aux::open_mode::write, 50'000'000 +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + , open_unmap_lock +#endif + ); + file_view view = map->view(); + auto range = view.range(); + TEST_CHECK(range.size() == 50'000'000); + + range[0] = 1; + range[49'999'999] = 1; + } + + // Find out if the filesystem we're running the test on supports sparse + // files. If not, we don't expect any of the files to be sparse + bool const supports_sparse_files = fs_supports_sparse_files(); + + // make sure "sparse-1" is actually sparse +#ifdef TORRENT_WINDOWS + DWORD high; + std::int64_t const original_size = ::GetCompressedFileSizeA("sparse-1", &high); + TEST_CHECK(original_size != INVALID_FILE_SIZE); + TEST_CHECK(high == 0); +#else + struct stat st; + TEST_CHECK(::stat("sparse-1", &st) == 0); + std::int64_t const original_size = st.st_blocks * 512; +#endif + printf("original_size: %d\n", int(original_size)); + if (supports_sparse_files) + { + TEST_CHECK(original_size < 500'000); + } + else + { + TEST_CHECK(original_size >= 50'000'000); + } + + lt::error_code ec; + lt::copy_file("sparse-1", "sparse-1.copy", ec); + TEST_CHECK(!ec); + + // make sure the copy is sparse +#ifdef TORRENT_WINDOWS + WIN32_FILE_ATTRIBUTE_DATA out_stat; + TEST_CHECK(::GetFileAttributesExA("sparse-1.copy", GetFileExInfoStandard, &out_stat)); + if (supports_sparse_files) + { + TEST_CHECK(out_stat.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE); + } + else + { + TEST_CHECK((out_stat.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) == 0); + } + + TEST_EQUAL(::GetCompressedFileSizeA("sparse-1.copy", &high), original_size); + TEST_CHECK(high == 0); +#else + TEST_CHECK(::stat("sparse-1.copy", &st) == 0); + printf("copy_size: %d\n", int(st.st_blocks) * 512); + TEST_EQUAL(st.st_blocks * 512, original_size); +#endif + + TEST_CHECK(compare_files("sparse-1", "sparse-1.copy")); +} +#endif + diff --git a/test/test_crc32.cpp b/test/test_crc32.cpp new file mode 100644 index 0000000..a59a10c --- /dev/null +++ b/test/test_crc32.cpp @@ -0,0 +1,71 @@ +/* + +Copyright (c) 2014-2017, 2019-2020, Arvid Norberg +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/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..346a005 --- /dev/null +++ b/test/test_create_torrent.cpp @@ -0,0 +1,555 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2016-2020, Arvid Norberg +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2017-2018, 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 "test_utils.hpp" +#include "setup_transfer.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 "libtorrent/units.hpp" + +#include +#include +#include +#include + +using namespace std::literals::string_literals; + +// 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(auto_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)); + } +} + +namespace { +int test_piece_size(int const piece_size, lt::create_flags_t const f = {}) +{ + std::int64_t const MiB = 1024 * 1024; + lt::file_storage fs; + fs.add_file("a", 100 * MiB); + lt::create_torrent ct(fs, piece_size, f); + return ct.piece_length(); +} +} + +TORRENT_TEST(piece_size_restriction_16kB) +{ + TEST_EQUAL(test_piece_size(15000), 16 * 1024); + TEST_EQUAL(test_piece_size(500), 16 * 1024); + TEST_THROW(test_piece_size(15000, lt::create_torrent::v1_only)); + TEST_THROW(test_piece_size(8000, lt::create_torrent::v1_only)); + TEST_EQUAL(test_piece_size(8192, lt::create_torrent::v1_only), 8192); +} + +TORRENT_TEST(piece_size_quanta) +{ + TEST_EQUAL(test_piece_size(32 * 1024), 32 * 1024); + TEST_EQUAL(test_piece_size(32 * 1024, lt::create_torrent::v1_only), 32 * 1024); + TEST_THROW(test_piece_size(48 * 1024)); + TEST_EQUAL(test_piece_size(48 * 1024, lt::create_torrent::v1_only), 48 * 1024); + TEST_THROW(test_piece_size(47 * 1024, lt::create_torrent::v1_only)); + TEST_THROW(test_piece_size(47 * 1024)); +} + +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_hashes() == info2.info_hashes()); + TEST_CHECK(info2.hash_for_piece(0_piece) == info1.hash_for_piece(0_piece)); +} + +namespace { + +void test_round_trip_torrent(std::string const& name) +{ + std::string const root_dir = lt::parent_path(lt::current_working_directory()); + std::string const filename = lt::combine_path(lt::combine_path(root_dir, "test_torrents"), name); + std::vector v2_buffer; + lt::error_code ec; + int const ret = load_file(filename, v2_buffer, ec); + TEST_CHECK(ret == 0); + TEST_CHECK(!ec); + + lt::bdecode_node in_torrent = lt::bdecode(v2_buffer); + + lt::torrent_info info1(v2_buffer, lt::from_span); + lt::create_torrent t(info1); + + std::vector out_buffer; + lt::entry e = t.generate(); + lt::bencode(std::back_inserter(out_buffer), e); + + lt::bdecode_node out_torrent = lt::bdecode(out_buffer); + + TEST_CHECK(out_torrent.dict_find("info").data_section() + == in_torrent.dict_find("info").data_section()); + + auto in_piece_layers = in_torrent.dict_find("piece layers").data_section(); + auto out_piece_layers = out_torrent.dict_find("piece layers").data_section(); + TEST_CHECK(out_piece_layers == in_piece_layers); +} + +} + +TORRENT_TEST(create_torrent_round_trip_v2) +{ + test_round_trip_torrent("v2_only.torrent"); +} + +TORRENT_TEST(create_torrent_round_trip_hybrid) +{ + test_round_trip_torrent("v2_hybrid.torrent"); +} + +TORRENT_TEST(create_torrent_round_trip_empty_file) +{ + test_round_trip_torrent("v2_empty_file.torrent"); +} + +// check that attempting to create a torrent containing both +// a file and directory with the same name is not allowed +TORRENT_TEST(v2_path_conflict) +{ + lt::file_storage fs; + + for (int i = 0; i < 2; ++i) + { + switch (i) + { + case 0: + fs.add_file("test/A/tmp", 0x4000); + fs.add_file("test/a", 0x4000); + fs.add_file("test/A", 0x4000); + fs.add_file("test/filler", 0x4000); + break; + case 1: + fs.add_file("test/long/path/name/that/collides", 0x4000); + fs.add_file("test/long/path", 0x4000); + fs.add_file("test/filler-1", 0x4000); + fs.add_file("test/filler-2", 0x4000); + break; + } + + lt::create_torrent t(fs, 0x4000); + lt::sha256_hash const dummy("01234567890123456789012345678901"); + lt::piece_index_t::diff_type zero(0); + t.set_hash2(0_file, zero, dummy); + t.set_hash2(1_file, zero, dummy); + t.set_hash2(2_file, zero, dummy); + t.set_hash2(3_file, zero, dummy); + TEST_THROW(t.generate()); + } +} + +TORRENT_TEST(v2_only) +{ + lt::file_storage fs; + fs.add_file("test/A", 0x8002); + fs.add_file("test/B", 0x4002); + lt::create_torrent t(fs, 0x4000, lt::create_torrent::v2_only); + + using p = lt::piece_index_t::diff_type; + t.set_hash2(0_file, p(0), lt::sha256_hash::max()); + t.set_hash2(0_file, p(1), lt::sha256_hash::max()); + t.set_hash2(0_file, p(2), lt::sha256_hash::max()); + // file 1 is a pad file + t.set_hash2(2_file, p(0), lt::sha256_hash::max()); + t.set_hash2(2_file, p(1), lt::sha256_hash::max()); + + std::vector buffer; + lt::bencode(std::back_inserter(buffer), t.generate()); + lt::torrent_info info(buffer, lt::from_span); + TEST_CHECK(info.info_hashes().has_v2()); + TEST_CHECK(!info.info_hashes().has_v1()); + TEST_EQUAL(info.files().file_name(0_file), "A"); + TEST_CHECK(info.files().pad_file_at(1_file)); + TEST_EQUAL(info.files().file_name(2_file), "B"); + TEST_EQUAL(info.name(), "test"); + + lt::create_torrent t2(info); + std::vector buffer2; + lt::bencode(std::back_inserter(buffer2), t2.generate()); + + TEST_CHECK(buffer == buffer2); +} + +TORRENT_TEST(v2_only_set_hash) +{ + lt::file_storage fs; + fs.add_file("test/A", 0x8002); + lt::create_torrent t(fs, 0x4000, lt::create_torrent::v2_only); + + TEST_THROW(t.set_hash(0_piece, lt::sha1_hash::max())); +} + +namespace { + +void check(int ret) +{ + if (ret == 0) return; + lt::error_code ec(errno, lt::generic_category()); + std::cerr << "call failed: " << ec.message() << '\n'; + throw std::runtime_error(ec.message()); +} + +} + +#if TORRENT_HAS_SYMLINK + +TORRENT_TEST(create_torrent_symlink) +{ + lt::error_code ec; + lt::create_directories("test-torrent/a/b/c", ec); + lt::create_directories("test-torrent/d/", ec); + std::ofstream f1("test-torrent/a/b/c/file-1"); + check(::truncate("test-torrent/a/b/c/file-1", 1000)); + std::ofstream f2("test-torrent/d/file-2"); + check(::truncate("test-torrent/d/file-2", 1000)); + check(::symlink("../a/b/c/file-1", "test-torrent/d/test-link-1")); + check(::symlink("a/b/c/file-1", "test-torrent/test-link-2")); + check(::symlink("a/b/c/file-1", "test-torrent/a/b/c/test-link-3")); + check(::symlink("../../../d/file-2", "test-torrent/a/b/c/test-link-4")); + + lt::file_storage fs; + lt::add_files(fs, "test-torrent" + , [](std::string n){ std::cout << n << '\n'; return true; }, lt::create_torrent::symlinks); + + lt::create_torrent t(fs, 16 * 1024, lt::create_torrent::symlinks); + lt::set_piece_hashes(t, ".", [] (lt::piece_index_t) {}); + + std::vector torrent; + lt::bencode(back_inserter(torrent), t.generate()); + + lt::torrent_info ti(torrent, lt::from_span); + + int found = 0; + for (auto i : ti.files().file_range()) + { + auto const filename = ti.files().file_path(i); + + if (filename == "test-torrent/d/test-link-1" + || filename == "test-torrent/test-link-2" + || filename == "test-torrent/a/b/c/test-link-3") + { + TEST_EQUAL(ti.files().symlink(i), "test-torrent/a/b/c/file-1"); + ++found; + } + else if (filename == "test-torrent/a/b/c/test-link-4") + { + TEST_EQUAL(ti.files().symlink(i), "test-torrent/d/file-2"); + ++found; + } + } + TEST_EQUAL(found, 4); +} + +#endif + +#ifndef TORRENT_WINDOWS + +TORRENT_TEST(v2_attributes) +{ + std::ofstream f1("file-1"); + check(::truncate("file-1", 1000)); + check(::chmod("file-1", S_IWUSR | S_IRUSR | S_IXUSR)); + + lt::file_storage fs; + lt::add_files(fs, "file-1", [](std::string){ return true; }, {}); + + lt::create_torrent t(fs, 16 * 1024, {}); + lt::set_piece_hashes(t, ".", [] (lt::piece_index_t) {}); + + lt::entry e = t.generate(); + + std::cout << e.to_string() << '\n'; + + TEST_EQUAL(e["info"]["attr"].string(), "x"); + TEST_EQUAL(e["info"]["file tree"]["file-1"][""]["attr"].string(), "x"); +} +#endif + +TORRENT_TEST(v1_only_set_hash2) +{ + lt::file_storage fs; + fs.add_file("test/A", 0x8002); + lt::create_torrent t(fs, 0x4000, lt::create_torrent::v1_only); + + using p = lt::piece_index_t::diff_type; + TEST_THROW(t.set_hash2(0_file, p(0), lt::sha256_hash::max())); +} + +// if we don't specify a v2-only flag, but only set v2 hashes, the created +// torrent is implicitly v2-only +TORRENT_TEST(implicit_v2_only) +{ + lt::file_storage fs; + fs.add_file("test/A", 0x8002); + fs.add_file("test/B", 0x4002); + lt::create_torrent t(fs, 0x4000); + + using p = lt::piece_index_t::diff_type; + t.set_hash2(0_file, p(0), lt::sha256_hash::max()); + t.set_hash2(0_file, p(1), lt::sha256_hash::max()); + t.set_hash2(0_file, p(2), lt::sha256_hash::max()); + // file 1 is a pad file + t.set_hash2(2_file, p(0), lt::sha256_hash::max()); + t.set_hash2(2_file, p(1), lt::sha256_hash::max()); + + std::vector buffer; + lt::bencode(std::back_inserter(buffer), t.generate()); + lt::torrent_info info(buffer, lt::from_span); + TEST_CHECK(info.info_hashes().has_v2()); + TEST_CHECK(!info.info_hashes().has_v1()); + TEST_EQUAL(info.files().file_name(0_file), "A"); + TEST_EQUAL(info.files().pad_file_at(1_file), true); + TEST_EQUAL(info.files().file_name(2_file), "B"); + TEST_EQUAL(info.name(), "test"); +} + +// if we don't specify a v1-only flag, but only set v1 hashes, the created +// torrent is implicitly v1-only +TORRENT_TEST(implicit_v1_only) +{ + lt::file_storage fs; + fs.add_file("test/A", 0x8002); + fs.add_file("test/B", 0x4002); + lt::create_torrent t(fs, 0x4000); + + for (lt::piece_index_t i : fs.piece_range()) + t.set_hash(i, lt::sha1_hash::max()); + + std::vector buffer; + lt::bencode(std::back_inserter(buffer), t.generate()); + lt::torrent_info info(buffer, lt::from_span); + TEST_CHECK(!info.info_hashes().has_v2()); + TEST_CHECK(info.info_hashes().has_v1()); + TEST_EQUAL(info.files().file_name(0_file), "A"); + TEST_EQUAL(info.files().pad_file_at(1_file), true); + TEST_EQUAL(info.files().file_name(2_file), "B"); + TEST_EQUAL(info.name(), "test"); +} + +namespace { + +template +lt::torrent_info test_field(Fun f) +{ + lt::file_storage fs; + fs.add_file("A", 0x4000); + lt::create_torrent t(fs, 0x4000); + for (lt::piece_index_t i : fs.piece_range()) + t.set_hash(i, lt::sha1_hash::max()); + + f(t); + + std::vector buffer; + lt::bencode(std::back_inserter(buffer), t.generate()); + return lt::torrent_info(buffer, lt::from_span); +} +} + +TORRENT_TEST(no_creation_date) +{ + auto info = test_field([](lt::create_torrent& t){ + t.set_creation_date(0); + }); + TEST_EQUAL(info.creation_date(), 0); +} + +TORRENT_TEST(creation_date) +{ + auto info = test_field([](lt::create_torrent& t){ + t.set_creation_date(1337); + }); + TEST_EQUAL(info.creation_date(), 1337); +} + +TORRENT_TEST(comment) +{ + auto info = test_field([](lt::create_torrent& t){ + t.set_comment("foobar"); + }); + TEST_EQUAL(info.comment(), "foobar"); +} + +TORRENT_TEST(creator) +{ + auto info = test_field([](lt::create_torrent& t){ + t.set_creator("foobar"); + }); + TEST_EQUAL(info.creator(), "foobar"); +} + +TORRENT_TEST(dht_nodes) +{ + auto info = test_field([](lt::create_torrent& t){ + t.add_node({"foobar"s, 1337}); + }); + using nodes = std::vector>; + TEST_CHECK((info.nodes() == nodes{{"foobar", 1337}})); +} + +TORRENT_TEST(ssl_cert) +{ + auto info = test_field([](lt::create_torrent& t){ + t.set_root_cert("foobar"); + }); + TEST_EQUAL(info.ssl_cert(), "foobar"); +} + +TORRENT_TEST(priv) +{ + auto info = test_field([](lt::create_torrent& t){ + t.set_priv(true); + }); + TEST_CHECK(info.priv()); +} + +TORRENT_TEST(piece_layer) +{ + lt::file_storage fs; + fs.add_file("test/large", 0x8000); + fs.add_file("test/small-1", 0x4000); + fs.add_file("test/small-2", 0x3fff); + lt::create_torrent t(fs, 0x4000); + + using p = lt::piece_index_t::diff_type; + t.set_hash2(0_file, p(0), lt::sha256_hash::max()); + t.set_hash2(0_file, p(1), lt::sha256_hash::max()); + t.set_hash2(1_file, p(0), lt::sha256_hash::max()); + t.set_hash2(2_file, p(0), lt::sha256_hash::max()); + + std::vector buffer; + lt::bencode(std::back_inserter(buffer), t.generate()); + lt::torrent_info info(buffer, lt::from_span); + + TEST_CHECK(info.piece_layer(0_file).size() == lt::sha256_hash::size() * 2); + TEST_CHECK(info.piece_layer(1_file).size() == lt::sha256_hash::size()); + TEST_CHECK(info.piece_layer(2_file).size() == lt::sha256_hash::size()); +} + +TORRENT_TEST(pieces_root_empty_file) +{ + lt::file_storage fs; + fs.add_file("test/1-empty", 0); + fs.add_file("test/2-small", 0x3fff); + fs.add_file("test/3-empty", 0); + lt::create_torrent t(fs, 0x4000); + + using p = lt::piece_index_t::diff_type; + t.set_hash2(1_file, p(0), lt::sha256_hash::max()); + + std::vector buffer; + lt::entry e = t.generate(); + TEST_CHECK(!e["info"]["files tree"]["test"]["1-empty"].find_key("pieces root")); + TEST_CHECK(!e["info"]["files tree"]["test"]["2-small"].find_key("pieces root")); + TEST_CHECK(!e["info"]["files tree"]["test"]["3-small"].find_key("pieces root")); + + lt::bencode(std::back_inserter(buffer), e); + lt::torrent_info info(buffer, lt::from_span); + + TEST_CHECK(info.files().root(0_file).is_all_zeros()); + TEST_CHECK(!info.files().root(1_file).is_all_zeros()); +} diff --git a/test/test_dht.cpp b/test/test_dht.cpp new file mode 100644 index 0000000..74eee51 --- /dev/null +++ b/test/test_dht.cpp @@ -0,0 +1,4089 @@ +/* + +Copyright (c) 2009-2020, Arvid Norberg +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2015-2019, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2020, Fonic +Copyright (c) 2020, FranciscoPombal +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/ip_helpers.hpp" +#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 "libtorrent/kademlia/dht_tracker.hpp" + +#include +#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::aux::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 = int(lt::random(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 +}; + +aux::session_settings test_settings() +{ + aux::session_settings sett; + sett.set_int(settings_pack::dht_max_torrents, 4); + sett.set_int(settings_pack::dht_max_dht_items, 4); + sett.set_bool(settings_pack::dht_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()}); + } + + aux::session_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) +{ + os << "kademlia routing table state\n" + "bucket_size: " << table.bucket_size() << "\n" + "global node count: " << table.num_global_nodes() << "\n" + "node_id: " << aux::to_hex(table.id()) << "\n\n" + "number of nodes per bucket:\n"; + + int idx = 0; + + for (auto i = table.buckets().begin(), end(table.buckets().end()); + i != end; ++i, ++idx) + { + os << std::setw(2) << idx << ": "; + for (int k = 0; k < int(i->live_nodes.size()); ++k) + os << "#"; + for (int k = 0; k < int(i->replacements.size()); ++k) + os << "-"; + os << "\n"; + } + + time_point now = aux::time_now(); + + os << "\nnodes:"; + + int bucket_index = 0; + for (auto i = table.buckets().begin(), end(table.buckets().end()); + i != end; ++i, ++bucket_index) + { + os << "\n=== BUCKET == " << bucket_index << " == " + << i->live_nodes.size() << "|" + << i->replacements.size() << " ==== \n"; + + 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); + + os << " prefix: " << std::hex << std::setw(2) << prefix << std::dec + << " id: " << aux::to_hex(j->id); + + if (j->rtt == 0xffff) + os << " rtt: "; + else + os << " rtt: " << std::setw(4) << j->rtt; + + os << " fail: " << std::setw(3) << j->fail_count() + << " ping: " << j->pinged() + << " dist: " << distance_exp(table.id(), j->id); + + if (j->last_queried == min_time()) + os << " query: "; + else + os << " query: " << std::setw(3) << total_seconds(now - j->last_queried); + + os << " ip: " << print_endpoint(j->ep()); + } + } + + os << "\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; + } + + os << std::setw(2) << bucket_index << ": ["; + + for (int j = 0; j < bucket_size_limit; ++j) + { + os << (sub_buckets[static_cast(j)] ? "X" : " "); + } + os << "]\n"; + } +} + +} // 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.set_bool(settings_pack::dht_enforce_node_id, true); + + node_id nid; + if (aux::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 (aux::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(), std::uint16_t(random(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 + aux::session_settings s; + s.set_bool(settings_pack::dht_extended_routing_table, false); + // s.set_bool(settings_pack::dht_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 (aux::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, {}, 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); + nodes = table.find_node(nid, {}, 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); + nodes = table.find_node(nid, {}, 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); + nodes = table.find_node(id, {}, 10); + } + TEST_EQUAL(table.bucket_size(0), 0); + TEST_EQUAL(nodes.size(), 0); + + s.set_bool(settings_pack::dht_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())); + + + { + node_id const id = generate_random_id(); + std::vector temp = table.find_node(id, {}, 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(); + std::vector temp = table.find_node(id, {}, 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 (auto const& e : temp) + { + TEST_CHECK(duplicates.count(e.id) == 0); + duplicates.insert(e.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 (aux::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 (aux::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 (aux::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 (aux::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 (aux::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.set_int(settings_pack::dht_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.set_int(settings_pack::dht_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.set_int(settings_pack::dht_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 + auto 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 = aux::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 = aux::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_node", source, &response + , msg_args().target(sha1_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 = aux::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 = aux::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 + auto sett = test_settings(); + obs observer; + + sett.set_bool(settings_pack::dht_extended_routing_table, false); + // it's difficult to generate valid nodes with specific node IDs, so just + // turn off that check + sett.set_bool(settings_pack::dht_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) +{ + auto sett = test_settings(); + obs observer; + + sett.set_bool(settings_pack::dht_extended_routing_table, false); + sett.set_bool(settings_pack::dht_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) +{ + auto sett = test_settings(); + obs observer; + sett.set_bool(settings_pack::dht_extended_routing_table, true); + sett.set_bool(settings_pack::dht_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) +{ + auto sett = test_settings(); + sett.set_bool(settings_pack::dht_enforce_node_id, false); + sett.set_bool(settings_pack::dht_extended_routing_table, false); + sett.set_bool(settings_pack::dht_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) +{ + auto sett = test_settings(); + obs observer; + + sett.set_bool(settings_pack::dht_extended_routing_table, false); + sett.set_bool(settings_pack::dht_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 + auto sett = test_settings(); + sett.set_bool(settings_pack::dht_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.set_bool(settings_pack::dht_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 + auto 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() override { 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 + auto 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 + aux::session_settings s; + s.set_bool(settings_pack::dht_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); + nodes = table.find_node(id, {}, 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); + nodes = table.find_node(id, {}, 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); + nodes = table.find_node(id, {}, 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(make_address("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(make_address("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(); + + node_id nid = generate_random_id(); + 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, + [nid, h1, ep1, h2, ep2](node_id const& node_id + , time_duration interval + , int num + , std::vector samples + , std::vector> const& nodes) + { + TEST_EQUAL(node_id, nid); + 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() + .nid(nid) + .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); + } +} + +namespace { +void test_rate_limit(aux::session_settings const& sett, std::function f) +{ + io_context ios; + obs observer; + counters cnt; + auto dht_storage = dht_default_storage_constructor(sett); + dht_state s; + + lt::dht::dht_tracker::send_fun_t send; + + dht_tracker dht(&observer, ios, send, sett, cnt, *dht_storage, std::move(s)); + f(dht); +} +} + +TORRENT_TEST(rate_limit_int_max) +{ + aux::session_settings sett; + sett.set_int(settings_pack::dht_upload_rate_limit, std::numeric_limits::max()); + + test_rate_limit(sett, [](lt::dht::socket_manager& sm) { + TEST_CHECK(sm.has_quota()); + std::this_thread::sleep_for(milliseconds(10)); + + // *any* increment to the quote above INT_MAX may overflow. Ensure it + // doesn't + TEST_CHECK(sm.has_quota()); + }); +} + +TORRENT_TEST(rate_limit_large_delta) +{ + aux::session_settings sett; + sett.set_int(settings_pack::dht_upload_rate_limit, std::numeric_limits::max()); + + test_rate_limit(sett, [](lt::dht::socket_manager& sm) { + TEST_CHECK(sm.has_quota()); + std::this_thread::sleep_for(milliseconds(2500)); + + // even though we have headroom in the limit itself, this long delay + // would increment the quota past INT_MAX + TEST_CHECK(sm.has_quota()); + }); +} + +TORRENT_TEST(rate_limit_accrue_limit) +{ + aux::session_settings sett; + sett.set_int(settings_pack::dht_upload_rate_limit, std::numeric_limits::max()); + + test_rate_limit(sett, [](lt::dht::socket_manager& sm) { + TEST_CHECK(sm.has_quota()); + for (int i = 0; i < 10; ++i) + { + std::this_thread::sleep_for(milliseconds(500)); + TEST_CHECK(sm.has_quota()); + } + }); +} + + +// 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..790b629 --- /dev/null +++ b/test/test_dht_storage.cpp @@ -0,0 +1,511 @@ +/* + +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2015-2018, Alden Torres +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/config.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/socket_io.hpp" // for hash_address +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/random.hpp" +#include "libtorrent/kademlia/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 +{ + aux::session_settings test_settings() + { + aux::session_settings sett; + sett.set_int(settings_pack::dht_max_torrents, 2); + sett.set_int(settings_pack::dht_max_dht_items, 2); + sett.set_int(settings_pack::dht_item_lifetime, int(seconds(120 * 60).count())); + return sett; + } + + bool g_storage_constructor_invoked = false; + + std::unique_ptr dht_custom_storage_constructor( + settings_interface const& settings) + { + g_storage_constructor_invoked = true; + return dht_default_storage_constructor(settings); + } + + std::unique_ptr create_default_dht_storage( + settings_interface 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) +{ + auto const 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) +{ + auto const 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) +{ + auto const 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) +{ + auto const 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) +{ + auto sett = test_settings(); + sett.set_int(settings_pack::dht_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) +{ + auto sett = test_settings(); + sett.set_int(settings_pack::dht_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) +{ + auto sett = test_settings(); + sett.set_int(settings_pack::dht_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) +{ + auto sett = test_settings(); + sett.set_int(settings_pack::dht_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 + auto sett = test_settings(); + sett.set_int(settings_pack::dht_max_peers, 2000); + sett.set_int(settings_pack::dht_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 = aux::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) +{ + auto const 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) +{ + auto sett = test_settings(); + sett.set_int(settings_pack::dht_max_torrents, 5); + sett.set_int(settings_pack::dht_sample_infohashes_interval, 10); + sett.set_int(settings_pack::dht_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.set_int(settings_pack::dht_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) +{ + auto sett = test_settings(); + sett.set_int(settings_pack::dht_max_torrents, 1000); + sett.set_int(settings_pack::dht_sample_infohashes_interval, 0); // need this to force refresh every call + sett.set_int(settings_pack::dht_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..b0c1a3b --- /dev/null +++ b/test/test_direct_dht.cpp @@ -0,0 +1,150 @@ +/* + +Copyright (c) 2015, Steven Siloti +Copyright (c) 2015-2020, Arvid Norberg +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" + +#if !defined TORRENT_DISABLE_EXTENSIONS && !defined TORRENT_DISABLE_DHT + +#include "libtorrent/config.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/bdecode.hpp" +#include "setup_transfer.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(session_params(sp, {})); + sp.set_str(settings_pack::listen_interfaces, "127.0.0.1:45434"); + lt::session requester(session_params(sp, {})); + + responder.add_extension(std::make_shared()); + + // successful request + + entry r; + r["q"] = "test_good"; + requester.dht_direct_request(uep("127.0.0.1", responder.listen_port()) + , r, client_data_t(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(), make_address("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.get(), reinterpret_cast(12345)); + } + + // failed request + + requester.dht_direct_request(uep("127.0.0.1", 53545) + , r, client_data_t(reinterpret_cast(123456))); + + ra = get_direct_response(requester); + TEST_CHECK(ra); + if (ra) + { + TEST_EQUAL(ra->endpoint.address(), make_address("127.0.0.1")); + TEST_EQUAL(ra->endpoint.port(), 53545); + TEST_EQUAL(ra->response().type(), bdecode_node::none_t); + TEST_EQUAL(ra->userdata.get(), 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..40131e6 --- /dev/null +++ b/test/test_dos_blocker.cpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2010, 2014-2017, 2019, Arvid Norberg +Copyright (c) 2016, 2018, 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 "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 = make_address_v4("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..592dd34 --- /dev/null +++ b/test/test_ed25519.cpp @@ -0,0 +1,290 @@ +/* + +Copyright (c) 2016-2017, 2019, Arvid Norberg +Copyright (c) 2016, 2018, 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..c254821 --- /dev/null +++ b/test/test_enum_net.cpp @@ -0,0 +1,278 @@ +/* + +Copyright (c) 2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2017, 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 "setup_transfer.hpp" // for supports_ipv6 +#include "libtorrent/enum_net.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" +#include "libtorrent/error_code.hpp" +#include + +using namespace lt; +using namespace lt::aux; +using boost::none; + +TORRENT_TEST(is_local) +{ + error_code ec; + TEST_CHECK(is_local(make_address("192.168.0.1", ec))); + TEST_CHECK(!ec); + TEST_CHECK(is_local(make_address("10.1.1.56", ec))); + TEST_CHECK(!ec); + TEST_CHECK(!is_local(make_address("14.14.251.63", ec))); + TEST_CHECK(!ec); +} + +TORRENT_TEST(match_addr_mask) +{ + TEST_CHECK(match_addr_mask( + make_address("10.0.1.176"), + make_address("10.0.1.176"), + make_address("255.255.255.0"))); + + TEST_CHECK(match_addr_mask( + make_address("10.0.1.3"), + make_address("10.0.3.3"), + make_address("255.255.0.0"))); + + TEST_CHECK(!match_addr_mask( + make_address("10.0.1.3"), + make_address("10.1.3.3"), + make_address("255.255.0.0"))); + + TEST_CHECK(match_addr_mask( + make_address("ff00:1234::"), + make_address("ff00:5678::"), + make_address("ffff::"))); + + TEST_CHECK(!match_addr_mask( + make_address("ff00:1234::"), + make_address("ff00:5678::"), + make_address("ffff:f000::"))); + + // different scope IDs always means a mismatch + TEST_CHECK(!match_addr_mask( + make_address("ff00:1234::%1"), + make_address("ff00:1234::%2"), + make_address("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 = make_address(ip); + ret.gateway = make_address(gateway); + ret.netmask = make_address(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 = make_address(addr); + ret.netmask = make_address("255.255.255.255"); + std::strncpy(ret.name, name, sizeof(ret.name) - 1); + 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) == make_address("192.168.0.1")); + TEST_CHECK(get_gateway(ip("2a02::4567", "eth0"), routes) == make_address("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) == make_address("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) == make_address("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) == make_address("192.168.0.1")); + TEST_CHECK(get_gateway(ip("10.0.1.130", "eth1"), routes) == make_address("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..68bd455 --- /dev/null +++ b/test/test_fast_extension.cpp @@ -0,0 +1,1136 @@ +/* + +Copyright (c) 2007-2009, 2011-2012, 2014-2020, Arvid Norberg +Copyright (c) 2016, 2018, Steven Siloti +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016-2018, 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 "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 "libtorrent/session.hpp" +#include "libtorrent/session_params.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().c_str(), buf); +} + +void print_session_log(lt::session& ses) +{ + print_alerts(ses, "ses", true); +} + +int read_message(tcp::socket& s, span buffer) +{ + using namespace lt::aux; + 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(aux::read_int32(ptr)); + r.start = aux::read_int32(ptr); + r.length = aux::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 = aux::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::aux; + 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::aux; + 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::aux; + + 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::aux; + + 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, info_hash_t 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.v1.data(), 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.v1.data(), 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::aux; + + 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::aux; + + 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::aux; + + 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, io_context& ioc + , info_hash_t& ih + , std::shared_ptr& ses, bool 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_hashes(); + + settings_pack sett = settings(); + sett.set_str(settings_pack::listen_interfaces, test_listen_interface()); + 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)); + + add_torrent_params p; + p.flags &= ~torrent_flags::paused; + p.flags &= ~torrent_flags::auto_managed; + p.flags |= flags; + if (magnet_link) + p.info_hashes = 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(ep("127.0.0.1", ses->listen_port()), ec); + if (ec) TEST_ERROR(ec.message()); + } + else + { + tcp::acceptor l(ioc); + l.open(tcp::v4()); + l.bind(ep("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; + + info_hash_t ih; + std::shared_ptr ses; + io_context ios; + tcp::socket s(ios); + setup_peer(s, ios, 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::aux; + 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; + + info_hash_t ih; + std::shared_ptr ses; + io_context ios; + tcp::socket s(ios); + setup_peer(s, ios, 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 = aux::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; + + info_hash_t ih; + std::shared_ptr ses; + io_context ios; + tcp::socket s(ios); + setup_peer(s, ios, 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::aux; + 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; + + info_hash_t ih; + std::shared_ptr ses; + io_context ios; + tcp::socket s(ios); + setup_peer(s, ios, 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::aux; + 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; + + info_hash_t ih; + std::shared_ptr ses; + io_context ios; + tcp::socket s(ios); + std::shared_ptr ti = setup_peer(s, ios, 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; + + info_hash_t ih; + std::shared_ptr ses; + io_context ios; + tcp::socket s(ios); + std::shared_ptr ti = setup_peer(s, ios, 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::aux; + + std::cout << "\n === test dont_have ===\n" << std::endl; + + info_hash_t ih; + torrent_handle th; + std::shared_ptr ses; + io_context ios; + tcp::socket s(ios); + std::shared_ptr ti = setup_peer(s, ios, 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[3_piece], false); + TEST_EQUAL(pi[0].pieces[2_piece], true); + TEST_EQUAL(pi[0].pieces[1_piece], true); + TEST_EQUAL(pi[0].pieces[0_piece], true); + + print_session_log(*ses); +} + +TORRENT_TEST(extension_handshake) +{ + using namespace lt::aux; + + info_hash_t ih; + std::shared_ptr ses; + io_context ios; + tcp::socket s(ios); + std::shared_ptr ti = setup_peer(s, ios, 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::aux; + + info_hash_t ih; + std::shared_ptr ses; + io_context ios; + tcp::socket s(ios); + std::shared_ptr ti = setup_peer(s, ios, 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; + + info_hash_t ih; + std::shared_ptr ses; + io_context ios; + tcp::socket s(ios); + setup_peer(s, ios, 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 = 124134235_piece; + req.start = 0; + req.length = 0x4000; + send_request(s, req); +} + +namespace { + +void have_all_test(bool const incoming) +{ + info_hash_t ih; + std::shared_ptr ses; + io_context ios; + tcp::socket s(ios); + setup_peer(s, ios, 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; + + info_hash_t ih; + std::shared_ptr ses; + io_context ios; + tcp::socket s(ios); + setup_peer(s, ios, 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..d9be52b --- /dev/null +++ b/test/test_fence.cpp @@ -0,0 +1,233 @@ +/* + +Copyright (c) 2014-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2020, 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/aux_/mmap_disk_job.hpp" +#include "libtorrent/aux_/disk_job_fence.hpp" +#include "libtorrent/performance_counters.hpp" +#include "test.hpp" + +#include + +using namespace lt; + +using lt::aux::disk_job_fence; +using lt::aux::mmap_disk_job; + +TORRENT_TEST(empty_fence) +{ + disk_job_fence fence; + counters cnt; + + mmap_disk_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], 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; + + mmap_disk_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], cnt); + // since we have outstanding jobs, no need + // to post anything + TEST_CHECK(ret_int == disk_job_fence::fence_post_none); + + 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); + + // 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; + + mmap_disk_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], cnt); + // since we have outstanding jobs, no need + // to post anything + TEST_CHECK(ret_int == disk_job_fence::fence_post_none); + + ret_int = fence.raise_fence(&test_job[7], 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); + + // 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 + 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..c76900c --- /dev/null +++ b/test/test_ffs.cpp @@ -0,0 +1,139 @@ +/* + +Copyright (c) 2016-2017, 2019-2020, Arvid Norberg +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" +#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..c8dabec --- /dev/null +++ b/test/test_file.cpp @@ -0,0 +1,616 @@ +/* + +Copyright (c) 2012-2020, Arvid Norberg +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017-2018, Andrei Kurushin +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_/directory.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/file_view_pool.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "test.hpp" +#include "test_utils.hpp" +#include +#include +#include +#include + +using namespace lt; + +namespace { + +void touch_file(std::string const& filename, int size) +{ + std::vector v; + v.resize(aux::numeric_cast(size)); + for (int i = 0; i < size; ++i) + v[std::size_t(i)] = char(i & 255); + + ofstream(filename.c_str()).write(v.data(), lt::aux::numeric_cast(v.size())); +} + +} // 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 (aux::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(); + + remove_all("file_test_dir", 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(path_equal("c:\\blah\\", "c:\\blah")); + TEST_CHECK(path_equal("c:\\blah", "c:\\blah")); + TEST_CHECK(path_equal("c:\\blah/", "c:\\blah")); + TEST_CHECK(path_equal("c:\\blah", "c:\\blah\\")); + TEST_CHECK(path_equal("c:\\blah", "c:\\blah")); + TEST_CHECK(path_equal("c:\\blah", "c:\\blah/")); + + TEST_CHECK(!path_equal("c:\\bla", "c:\\blah/")); + TEST_CHECK(!path_equal("c:\\bla", "c:\\blah")); + TEST_CHECK(!path_equal("c:\\blah", "c:\\bla")); + TEST_CHECK(!path_equal("c:\\blah\\sdf", "c:\\blah")); +#else + TEST_CHECK(path_equal("/blah", "/blah")); + TEST_CHECK(path_equal("/blah/", "/blah")); + TEST_CHECK(path_equal("/blah", "/blah")); + TEST_CHECK(path_equal("/blah", "/blah/")); + + TEST_CHECK(!path_equal("/bla", "/blah/")); + TEST_CHECK(!path_equal("/bla", "/blah")); + TEST_CHECK(!path_equal("/blah", "/bla")); + TEST_CHECK(!path_equal("/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()); + +#ifdef TORRENT_WINDOWS + TEST_EQUAL(complete(".\\foobar"), current_working_directory() + "\\foobar"); +#else + TEST_EQUAL(complete("./foobar"), current_working_directory() + "/foobar"); +#endif +} + +TORRENT_TEST(path_compare) +{ + TEST_EQUAL(path_compare("a/b/c", "x", "a/b/c", "x"), 0); + + // the path and filenames are implicitly concatenated when compared + TEST_CHECK(path_compare("a/b/", "a", "a/b/c", "a") < 0); + TEST_CHECK(path_compare("a/b/c", "a", "a/b/", "a") > 0); + + // if one path is shorter and a substring of the other, they are considered + // equal. This case is invalid for the purposes of sorting files in v2 + // torrents and will fail anyway + TEST_EQUAL(path_compare("a/b/", "c", "a/b/c", "a"), 0); + TEST_EQUAL(path_compare("a/b/c", "a", "a/b", "c"), 0); + + TEST_CHECK(path_compare("foo/b/c", "x", "a/b/c", "x") > 0); + TEST_CHECK(path_compare("a/b/c", "x", "foo/b/c", "x") < 0); + TEST_CHECK(path_compare("aaa/b/c", "x", "a/b/c", "x") > 0); + TEST_CHECK(path_compare("a/b/c", "x", "aaa/b/c", "x") < 0); + TEST_CHECK(path_compare("a/b/c/2", "x", "a/b/c/1", "x") > 0); + TEST_CHECK(path_compare("a/b/c/1", "x", "a/b/c/2", "x") < 0); + TEST_CHECK(path_compare("a/1/c", "x", "a/2/c", "x") < 0); + TEST_CHECK(path_compare("a/a/c", "x", "a/aa/c", "x") < 0); + TEST_CHECK(path_compare("a/aa/c", "x", "a/a/c", "x") > 0); +} + +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", "")); +} + +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 + lt::span str = "abcdefghijklmnopqrstuvwxyz"; + ofstream("original_file").write(str.data(), str.size()); + + error_code ec; + 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()); + + + char test_buf[27] = {}; + std::ifstream("second_link").read(test_buf, 27); + TEST_CHECK(test_buf == "abcdefghijklmnopqrstuvwxyz"_sv); + + 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(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(exists(special_name)); + lt::remove(special_name, ec); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(!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(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(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(!exists(long_file_name1)); + TEST_CHECK(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(exists(long_file_name1)); + + std::set files; + + for (lt::aux::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(!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(exists(long_file_name1)); + + lt::remove(long_file_name1, ec); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(!exists(long_file_name1)); + } +} + +TORRENT_TEST(unc_paths) +{ + std::string const reserved_name = "con"; + error_code ec; + { + file f(reserved_name, aux::open_mode::write, ec); + TEST_CHECK(!ec); + } + remove(reserved_name, ec); + TEST_CHECK(!ec); +} + +TORRENT_TEST(to_file_open_mode) +{ + TEST_CHECK(aux::to_file_open_mode(aux::open_mode::write) == file_open_mode::read_write); + TEST_CHECK(aux::to_file_open_mode({}) == file_open_mode::read_only); + TEST_CHECK(aux::to_file_open_mode(aux::open_mode::no_atime) == (file_open_mode::read_only | file_open_mode::no_atime)); + TEST_CHECK(aux::to_file_open_mode(aux::open_mode::write | aux::open_mode::no_atime) == (file_open_mode::read_write | file_open_mode::no_atime)); +} + + +#endif diff --git a/test/test_file_progress.cpp b/test/test_file_progress.cpp new file mode 100644 index 0000000..2172d55 --- /dev/null +++ b/test/test_file_progress.cpp @@ -0,0 +1,180 @@ +/* + +Copyright (c) 2015-2019, Arvid Norberg +Copyright (c) 2018-2019, 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_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(aux::calc_num_pieces(fs)); + + for (auto const idx : fs.piece_range()) + { + piece_picker picker(fs.total_size(), fs.piece_length()); + 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)); + TEST_EQUAL(sum, fp.total_on_disk()); + } +} + +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(aux::calc_num_pieces(fs)); + + for (auto const idx : fs.piece_range()) + { + piece_picker picker(fs.total_size(), fs.piece_length()); + 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)); + TEST_EQUAL(sum, fp.total_on_disk()); + } +} + +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(aux::calc_num_pieces(fs)); + + piece_picker picker(fs.total_size(), fs.piece_length()); + + 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()); +} + +TORRENT_TEST(pad_file_completion_callback) +{ + int const piece_size = 256; + + file_storage fs; + fs.add_file("torrent/1", 100000); + fs.add_file("torrent/2", 100, file_storage::flag_pad_file); + fs.add_file("torrent/3", 45000); + fs.set_piece_length(piece_size); + fs.set_num_pieces(aux::calc_num_pieces(fs)); + + piece_picker picker(fs.total_size(), fs.piece_length()); + + 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) + { + // there is no callback for the pad-file, and it also doesn't count + // as "total_on_disk()" + if (count == 0) TEST_EQUAL(fp.total_on_disk(), 100000); + else if (count == 1) TEST_EQUAL(fp.total_on_disk(), 145000); + + count++; + + aux::vector vec; + fp.export_progress(vec); + + TEST_EQUAL(vec[file_index], fs.file_size(file_index)); + }); + } + + TEST_EQUAL(count, 2); +} diff --git a/test/test_file_storage.cpp b/test/test_file_storage.cpp new file mode 100644 index 0000000..3ff78be --- /dev/null +++ b/test/test_file_storage.cpp @@ -0,0 +1,1188 @@ +/* + +Copyright (c) 2013, 2015-2017, 2019, Arvid Norberg +Copyright (c) 2017, 2019, Steven Siloti +Copyright (c) 2018, 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 "test.hpp" +#include "setup_transfer.hpp" +#include "test_utils.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(aux::calc_num_pieces(st)); + + 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); +} + +constexpr aux::path_index_t operator""_path(unsigned long long i) +{ return aux::path_index_t(static_cast(i)); } + +} // anonymous namespace + +TORRENT_TEST(coalesce_path) +{ + file_storage st; + st.set_piece_length(0x4000); + st.add_file(combine_path("test", "a"), 10000); + TEST_EQUAL(st.paths().size(), 1); + TEST_EQUAL(st.paths()[0_path], ""); + st.add_file(combine_path("test", "b"), 20000); + TEST_EQUAL(st.paths().size(), 1); + TEST_EQUAL(st.paths()[0_path], ""); + st.add_file(combine_path("test", combine_path("c", "a")), 30000); + TEST_EQUAL(st.paths().size(), 2); + TEST_EQUAL(st.paths()[0_path], ""); + TEST_EQUAL(st.paths()[1_path], "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_path], ""); + TEST_EQUAL(st.paths()[1_path], "c"); + + // cause pad files to be created, to make sure the pad files also share the + // same path entries + st.canonicalize(); + + TEST_EQUAL(st.paths().size(), 3); + TEST_EQUAL(st.paths()[0_path], ""); + TEST_EQUAL(st.paths()[1_path], "c"); + TEST_EQUAL(st.paths()[2_path], ".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; + st.set_piece_length(16 * 1024); + char const filename[] = "test1fooba"; + char const filehash[] = "01234567890123456789-----"; + char const roothash[] = "01234567890123456789012345678912-----"; + + st.add_file_borrow({filename, 5}, combine_path("test-torrent-1", "test1") + , 10, file_flags_t{}, filehash, 0, {}, roothash); + + // test filename_ptr and filename_len +#ifndef TORRENT_NO_DEPRECATE + TEST_EQUAL(st.file_name_ptr(file_index_t{0}), filename); + TEST_EQUAL(st.file_name_len(file_index_t{0}), 5); +#endif + TEST_EQUAL(st.file_name(file_index_t{0}), string_view(filename, 5)); + TEST_EQUAL(st.hash(file_index_t{0}), sha1_hash(filehash)); + TEST_EQUAL(st.root(file_index_t{0}), sha256_hash(roothash)); + + 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"))); +} + +TORRENT_TEST(invalid_path1) +{ + file_storage st; + st.set_piece_length(16 * 1024); +#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; + st.set_piece_length(16 * 1024); +#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, 0_piece); + TEST_EQUAL(rq.start, 0); + TEST_EQUAL(rq.length, 10); + rq = fs.map_file(file_index_t{5}, 0, 10); + TEST_EQUAL(rq.piece, 7_piece); + TEST_EQUAL(rq.start, 298); + TEST_EQUAL(rq.length, 10); + rq = fs.map_file(file_index_t{5}, 0, 1000); + TEST_EQUAL(rq.piece, 7_piece); + 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(0_file).c_str()); + std::printf("file: %s\n", fs.file_path(1_file).c_str()); + std::uint32_t file_hash0 = fs.file_path_hash(0_file, "a"); + std::uint32_t file_hash1 = fs.file_path_hash(1_file, "a"); + TEST_EQUAL(file_hash0, file_hash1); +} + +// make sure we fill in padding with small files +TORRENT_TEST(canonicalize_pad) +{ + file_storage fs; + fs.set_piece_length(0x4000); + fs.add_file(combine_path("s", "2"), 0x7000); + fs.add_file(combine_path("s", "1"), 1); + fs.add_file(combine_path("s", "3"), 0x7001); + + fs.canonicalize(); + + TEST_EQUAL(fs.num_files(), 5); + + TEST_EQUAL(fs.file_size(0_file), 1); + TEST_EQUAL(fs.file_name(0_file), "1"); + TEST_EQUAL(fs.pad_file_at(0_file), false); + + TEST_EQUAL(fs.file_size(1_file), 0x4000 - 1); + TEST_EQUAL(fs.pad_file_at(1_file), true); + + TEST_EQUAL(fs.file_size(2_file), 0x7000); + TEST_EQUAL(fs.file_name(2_file), "2"); + TEST_EQUAL(fs.pad_file_at(2_file), false); + + TEST_EQUAL(fs.file_size(3_file), 0x8000 - 0x7000); + TEST_EQUAL(fs.pad_file_at(3_file), true); + + TEST_EQUAL(fs.file_size(4_file), 0x7001); + TEST_EQUAL(fs.file_name(4_file), "3"); + TEST_EQUAL(fs.pad_file_at(4_file), false); +} + +// make sure canonicalize sorts by path correctly +TORRENT_TEST(canonicalize_path) +{ + file_storage fs; + fs.set_piece_length(0x4000); + fs.add_file(combine_path("b", combine_path("2", "a")), 0x4000); + fs.add_file(combine_path("b", combine_path("1", "a")), 0x4000); + fs.add_file(combine_path("b", combine_path("3", "a")), 0x4000); + fs.add_file(combine_path("b", "11"), 0x4000); + + fs.canonicalize(); + + TEST_EQUAL(fs.num_files(), 4); + + TEST_EQUAL(fs.file_path(0_file), combine_path("b", combine_path("1", "a"))); + TEST_EQUAL(fs.file_path(1_file), combine_path("b", "11")); + TEST_EQUAL(fs.file_path(2_file), combine_path("b", combine_path("2", "a"))); + TEST_EQUAL(fs.file_path(3_file), combine_path("b", combine_path("3", "a"))); +} + +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(aux::calc_num_pieces(fs)); + // +---+---+---+---+---+---+---+---+---+ + // pieces | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | + // +---+---+---+---+---+---+---+---+---+ + // files | 0 | 1 | 2 | + // +---+----------------+--------------+ + + TEST_CHECK(aux::file_piece_range_exclusive(fs, 0_file) == std::make_tuple(0_piece, 1_piece)); + TEST_CHECK(aux::file_piece_range_exclusive(fs, 1_file) == std::make_tuple(1_piece, 5_piece)); + TEST_CHECK(aux::file_piece_range_exclusive(fs, 2_file) == std::make_tuple(6_piece, 9_piece)); +} + +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(aux::calc_num_pieces(fs)); + // +---+---+---+---+---+---+---+---+---+ + // pieces | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | + // +---+---+---+---+---+---+---+---+---+ + // files | 0 | 1 | 2 | + // +---+----------------+--------------+ + + TEST_CHECK(aux::file_piece_range_inclusive(fs, 0_file) == std::make_tuple(0_piece, 1_piece)); + TEST_CHECK(aux::file_piece_range_inclusive(fs, 1_file) == std::make_tuple(1_piece, 6_piece)); + TEST_CHECK(aux::file_piece_range_inclusive(fs, 2_file) == std::make_tuple(5_piece, 9_piece)); +} + +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(aux::calc_num_pieces(fs)); + // +---+---+---+---+---+---+---+ + // pieces | 0 | 1 | 2 | 3 | 4 | 5 | 6 | + // +---+---+---+---+---+---+---+ + // files | 0 | 1 | + // +---+-------+------------+ + + TEST_CHECK(aux::file_piece_range_inclusive(fs, 0_file) == std::make_tuple(0_piece, 3_piece)); + TEST_CHECK(aux::file_piece_range_inclusive(fs, 1_file) == std::make_tuple(3_piece, 7_piece)); + + TEST_CHECK(aux::file_piece_range_exclusive(fs, 0_file) == std::make_tuple(0_piece, 3_piece)); + TEST_CHECK(aux::file_piece_range_exclusive(fs, 1_file) == std::make_tuple(3_piece, 7_piece)); +} + +TORRENT_TEST(piece_size_last_piece) +{ + file_storage fs; + fs.set_piece_length(1024); + fs.add_file("0", 100); + fs.set_num_pieces(aux::calc_num_pieces(fs)); + TEST_EQUAL(fs.piece_size(0_piece), 100); +} + +TORRENT_TEST(piece_size_middle_piece) +{ + file_storage fs; + fs.set_piece_length(1024); + fs.add_file("0", 2000); + fs.set_num_pieces(aux::calc_num_pieces(fs)); + TEST_EQUAL(fs.piece_size(0_piece), 1024); + TEST_EQUAL(fs.piece_size(1_piece), 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(aux::calc_num_pieces(fs)); + 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(0_piece, 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(aux::calc_num_pieces(fs)); + 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(0_piece, 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); +} + +TORRENT_TEST(query_symlinks2) +{ + file_storage fs; + fs.set_piece_length(1024); + fs.add_file("test/0", 10); + fs.add_file("test/1", 10); + fs.add_file("test/2", 10); + fs.add_file("test/3", 10); + + TEST_CHECK(fs.symlink(file_index_t{0}).empty()); + TEST_CHECK(fs.symlink(file_index_t{1}).empty()); + TEST_CHECK(fs.symlink(file_index_t{2}).empty()); + TEST_CHECK(fs.symlink(file_index_t{3}).empty()); +} + +TORRENT_TEST(files_compatible) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + fs1.add_file("test/0", 1); + fs1.add_file("test/1", 2); + + file_storage fs2; + fs2.set_piece_length(0x4000); + fs2.add_file("test/0", 1); + fs2.add_file("test/1", 2); + + TEST_CHECK(lt::aux::files_compatible(fs1, fs2)); +} + +TORRENT_TEST(files_compatible_num_files) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + fs1.add_file("test/0", 1); + fs1.add_file("test/1", 2); + + file_storage fs2; + fs2.set_piece_length(0x4000); + fs2.add_file("test/0", 3); + + TEST_CHECK(!lt::aux::files_compatible(fs1, fs2)); +} + +TORRENT_TEST(files_compatible_size) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + fs1.add_file("test/0", 2); + fs1.add_file("test/1", 1); + + file_storage fs2; + fs2.set_piece_length(0x4000); + fs2.add_file("test/0", 1); + fs2.add_file("test/1", 2); + + TEST_CHECK(!lt::aux::files_compatible(fs1, fs2)); +} + +TORRENT_TEST(files_compatible_name) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + fs1.add_file("test/1", 1); + fs1.add_file("test/0", 2); + + file_storage fs2; + fs2.set_piece_length(0x4000); + fs2.add_file("test/0", 1); + fs2.add_file("test/1", 2); + + TEST_CHECK(!lt::aux::files_compatible(fs1, fs2)); +} + +TORRENT_TEST(files_compatible_hidden) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + fs1.add_file("test/0", 1); + fs1.add_file("test/1", 2, file_storage::flag_hidden); + + file_storage fs2; + fs2.set_piece_length(0x4000); + fs2.add_file("test/0", 1); + fs2.add_file("test/1", 2); + + // hidden attribute does not affect compatibility + TEST_CHECK(lt::aux::files_compatible(fs1, fs2)); +} + +TORRENT_TEST(files_compatible_pad) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + fs1.add_file("test/0", 1); + fs1.add_file("test/1", 2, file_storage::flag_pad_file); + + file_storage fs2; + fs2.set_piece_length(0x4000); + fs2.add_file("test/0", 1); + fs2.add_file("test/1", 2); + + // pad attribute does affect compatibility + TEST_CHECK(!lt::aux::files_compatible(fs1, fs2)); +} + +TORRENT_TEST(files_compatible_empty_file_order) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + fs1.add_file("test/0", 1); + fs1.add_file("test/1", 0); + fs1.add_file("test/2", 0); + fs1.add_file("test/3", 0); + + file_storage fs2; + fs2.set_piece_length(0x4000); + fs2.add_file("test/0", 1); + fs2.add_file("test/3", 0); + fs2.add_file("test/2", 0); + fs2.add_file("test/1", 0); + + // order of empty files does not affect compatibility + TEST_CHECK(lt::aux::files_compatible(fs1, fs2)); +} + +TORRENT_TEST(files_compatible_mtime) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + fs1.add_file("test/0", 1, {}, 1234); + fs1.add_file("test/1", 2, {}, 1235); + + file_storage fs2; + fs2.set_piece_length(0x4000); + fs2.add_file("test/0", 1, {}, 1234); + fs2.add_file("test/1", 2, {}, 1234); + + // mtime does not affect compatibility + TEST_CHECK(lt::aux::files_compatible(fs1, fs2)); +} + +TORRENT_TEST(files_compatible_piece_size) +{ + file_storage fs1; + fs1.set_piece_length(0x8000); + fs1.add_file("test/0", 1); + fs1.add_file("test/1", 2); + + file_storage fs2; + fs2.set_piece_length(0x4000); + fs2.add_file("test/0", 1); + fs2.add_file("test/1", 2); + + TEST_CHECK(!lt::aux::files_compatible(fs1, fs2)); +} + +TORRENT_TEST(files_compatible_different_symlink) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + fs1.add_file("test/0", 1); + fs1.add_file("test/1", 2, file_storage::flag_symlink, 0, "test/0"); + + file_storage fs2; + fs2.set_piece_length(0x4000); + fs2.add_file("test/0", 1); + fs2.add_file("test/1", 2, file_storage::flag_symlink, 0, "test/1"); + + TEST_CHECK(!lt::aux::files_compatible(fs1, fs2)); +} + +TORRENT_TEST(files_compatible_same_symlink) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + fs1.add_file("test/0", 1); + fs1.add_file("test/1", 2, file_storage::flag_symlink, 0, "test/0"); + + file_storage fs2; + fs2.set_piece_length(0x4000); + fs2.add_file("test/0", 1); + fs2.add_file("test/1", 2, file_storage::flag_symlink, 0, "test/0"); + + TEST_CHECK(lt::aux::files_compatible(fs1, fs2)); +} + +TORRENT_TEST(remove_tail_padding_not_last) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + fs1.add_file("test/0", 1); + fs1.add_file("test/1", 2, file_storage::flag_pad_file); + fs1.add_file("test/2", 0); + fs1.add_file("test/3", 0); + + fs1.remove_tail_padding(); + + file_storage fs2; + fs2.set_piece_length(0x4000); + fs2.add_file("test/0", 1); + fs2.add_file("test/2", 0); + fs2.add_file("test/3", 0); + + TEST_CHECK(lt::aux::files_compatible(fs1, fs2)); +} + +TORRENT_TEST(remove_tail_padding_last) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + fs1.add_file("test/0", 1); + fs1.add_file("test/1", 2, file_storage::flag_pad_file); + + fs1.remove_tail_padding(); + + file_storage fs2; + fs2.set_piece_length(0x4000); + fs2.add_file("test/0", 1); + + TEST_CHECK(lt::aux::files_compatible(fs1, fs2)); +} + +TORRENT_TEST(remove_tail_padding_no_op) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + fs1.add_file("test/0", 1); + fs1.add_file("test/1", 0); + fs1.add_file("test/2", 0); + fs1.add_file("test/3", 0); + + fs1.remove_tail_padding(); + + file_storage fs2; + fs2.set_piece_length(0x4000); + fs2.add_file("test/0", 1); + fs2.add_file("test/1", 0); + fs2.add_file("test/2", 0); + fs2.add_file("test/3", 0); + + TEST_CHECK(lt::aux::files_compatible(fs1, fs2)); +} + + +std::int64_t const int_max = std::numeric_limits::max(); + +TORRENT_TEST(large_files) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + TEST_THROW(fs1.add_file("test/0", int_max / 2 * lt::default_block_size + 1)); + + error_code ec; + fs1.add_file(ec, "test/0", int_max * lt::default_block_size + 1); + TEST_EQUAL(ec, make_error_code(boost::system::errc::file_too_large)); + + // should not throw + TEST_NOTHROW(fs1.add_file("test/0", int_max / 2 * lt::default_block_size)); +} + +TORRENT_TEST(large_offset) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + for (int i = 0; i < 16; ++i) + fs1.add_file(("test/" + std::to_string(i)).c_str(), int_max / 2 * lt::default_block_size); + + // this exceeds the 2^48-1 limit + TEST_THROW(fs1.add_file("test/16", 262144)); + + error_code ec; + fs1.add_file(ec, "test/8", 262144); + TEST_EQUAL(ec, make_error_code(errors::torrent_invalid_length)); + + // this should be OK, but just + fs1.add_file("test/8", 262143); +} + +TORRENT_TEST(large_filename) +{ + file_storage fs1; + fs1.set_piece_length(0x4000); + // yes, this creates an invalid string_view, as it claims to be larger than + // the allocation. This should be OK though as the test for size never + // actually looks at the string + TEST_THROW(fs1.add_file_borrow(string_view("0", 1 << 12), "test/path/", 10)); + + error_code ec; + fs1.add_file_borrow(ec, string_view("0", 1 << 12), "test/path/", 10); + TEST_EQUAL(ec, make_error_code(boost::system::errc::filename_too_long)); +} + +TORRENT_TEST(piece_size2) +{ + file_storage fs; + fs.set_piece_length(0x8000); + // passing in a root hash (the last argument) makes it follow v2 rules, to + // add pad files + fs.add_file("test/0", 0x5000, {}, 0, {}, "01234567890123456789012345678901"); + + fs.set_num_pieces(aux::calc_num_pieces(fs)); + TEST_EQUAL(fs.num_pieces(), 1); + TEST_EQUAL(fs.piece_size2(0_piece), 0x5000); + + fs.add_file("test/1", 0x2000, {}, 0, {}, "01234567890123456789012345678901"); + fs.add_file("test/2", 0x8000, {}, 0, {}, "01234567890123456789012345678901"); + + fs.set_num_pieces(aux::calc_num_pieces(fs)); + TEST_EQUAL(fs.num_pieces(), 3); + TEST_EQUAL(fs.piece_size2(2_piece), 0x8000); + + fs.add_file("test/3", 8, {}, 0, {}, "01234567890123456789012345678901"); + + fs.set_num_pieces(aux::calc_num_pieces(fs)); + TEST_EQUAL(fs.num_pieces(), 4); + TEST_EQUAL(fs.piece_size2(0_piece), 0x5000); + TEST_EQUAL(fs.piece_size2(1_piece), 0x2000); + TEST_EQUAL(fs.piece_size2(2_piece), 0x8000); + TEST_EQUAL(fs.piece_size2(3_piece), 8); + + fs.add_file("test/4", 0x8001, {}, 0, {}, "01234567890123456789012345678901"); + + fs.set_num_pieces(aux::calc_num_pieces(fs)); + TEST_EQUAL(fs.num_pieces(), 6); + + TEST_EQUAL(fs.piece_size2(0_piece), 0x5000); + TEST_EQUAL(fs.piece_size2(1_piece), 0x2000); + TEST_EQUAL(fs.piece_size2(2_piece), 0x8000); + TEST_EQUAL(fs.piece_size2(3_piece), 8); + TEST_EQUAL(fs.piece_size2(4_piece), 0x8000); + TEST_EQUAL(fs.piece_size2(5_piece), 1); +} + +TORRENT_TEST(file_num_blocks) +{ + file_storage fs; + fs.set_piece_length(0x8000); + fs.add_file("test/0", 0x5000, {}, 0, {}, "01234567890123456789012345678901"); + fs.add_file("test/1", 0x2000, {}, 0, {}, "01234567890123456789012345678901"); + fs.add_file("test/2", 0x8000, {}, 0, {}, "01234567890123456789012345678901"); + fs.add_file("test/3", 0x8001, {}, 0, {}, "01234567890123456789012345678901"); + fs.add_file("test/4", 1, {}, 0, {}, "01234567890123456789012345678901"); + + // generally the number of blocks in a file is: + // (file_size + lt::default_block_size - 1) / lt::default_block_size + + TEST_EQUAL(fs.file_num_blocks(file_index_t{0}), 2); + // pad file at index 1 + TEST_EQUAL(fs.file_num_blocks(file_index_t{2}), 1); + // pad file at index 3 + TEST_EQUAL(fs.file_num_blocks(file_index_t{4}), 2); + TEST_EQUAL(fs.file_num_blocks(file_index_t{5}), 3); + // pad file at index 6 + TEST_EQUAL(fs.file_num_blocks(file_index_t{7}), 1); +} + +TORRENT_TEST(file_num_pieces) +{ + file_storage fs; + fs.set_piece_length(0x8000); + fs.add_file("test/0", 0x5000, {}, 0, {}, "01234567890123456789012345678901"); + fs.add_file("test/1", 0x2000, {}, 0, {}, "01234567890123456789012345678901"); + fs.add_file("test/2", 0x8000, {}, 0, {}, "01234567890123456789012345678901"); + fs.add_file("test/3", 0x8001, {}, 0, {}, "01234567890123456789012345678901"); + fs.add_file("test/4", 1, {}, 0, {}, "01234567890123456789012345678901"); + + // generally the number of blocks in a file is: + // (file_size + lt::default_block_size - 1) / lt::default_block_size + + TEST_EQUAL(fs.file_num_pieces(file_index_t{0}), 1); + // pad file at index 1 + TEST_EQUAL(fs.file_num_pieces(file_index_t{2}), 1); + // pad file at index 3 + TEST_EQUAL(fs.file_num_pieces(file_index_t{4}), 1); + TEST_EQUAL(fs.file_num_pieces(file_index_t{5}), 2); + // pad file at index 6 + TEST_EQUAL(fs.file_num_pieces(file_index_t{7}), 1); +} + +namespace { +int first_piece_node(int piece_size, int file_size) +{ + file_storage fs; + fs.set_piece_length(piece_size); + fs.add_file("test/0", file_size, {}, 0, {}, "01234567890123456789012345678901"); + fs.set_num_pieces(aux::calc_num_pieces(fs)); + return fs.file_first_piece_node(file_index_t{0}); +} + +int first_block_node(int file_size) +{ + file_storage fs; + fs.set_piece_length(0x10000); + fs.add_file("test/0", file_size, {}, 0, {}, "01234567890123456789012345678901"); + fs.set_num_pieces(aux::calc_num_pieces(fs)); + return fs.file_first_block_node(file_index_t{0}); +} +} + +TORRENT_TEST(file_first_piece_node) +{ + // the size of the merkle tree is implied by the size of the file. + // 0x500000 / 0x10000 = 80 pieces + // a merkle tree must have a power of 2 number of leaves, so that's 128, + // thats 7 layers + TEST_EQUAL(first_piece_node(0x10000, 0x500000), 127); + TEST_EQUAL(first_piece_node(0x8000, 0x500000), 255); + TEST_EQUAL(first_piece_node(0x4000, 0x500000), 511); + TEST_EQUAL(first_piece_node(0x2000, 0x500000), 1023); + TEST_EQUAL(first_piece_node(0x1000, 0x500000), 2047); + + // also test boundary cases around exact power of two file size + // technically piece size is not allowed to be less than 16kB + TEST_EQUAL(first_piece_node(0x1000, 0x7fffff), 2047); + TEST_EQUAL(first_piece_node(0x1000, 0x800000), 2047); + TEST_EQUAL(first_piece_node(0x1000, 0x800001), 4095); + + TEST_EQUAL(first_piece_node(0x1000, 0x7fff), 7); + TEST_EQUAL(first_piece_node(0x1000, 0x8000), 7); + TEST_EQUAL(first_piece_node(0x1000, 0x8001), 15); + + // edge case of file smaller than one block + TEST_EQUAL(first_piece_node(0x1000, 0x1000), 0); + + // edge case of file smaller than one piece + TEST_EQUAL(first_piece_node(0x4000, 0x1000), 0); +} + +TORRENT_TEST(file_first_block_node) +{ + // the full merkle tree, all the way down to blocks, does not depend on the + // piece size. Blocks are always 0x4000 bytes. + + // there must be an even power of two number of leaves, e.g. + // file size 0x500000 / 0x4000 = 320 blocks -> 512 leaves + TEST_EQUAL(first_block_node(0x500000), 511); + + // edge case of file smaller than one block + TEST_EQUAL(first_block_node(0x1000), 0); + + // even power-of-two boundary condition + TEST_EQUAL(first_block_node(0x7fffff), 511); + TEST_EQUAL(first_block_node(0x800000), 511); + TEST_EQUAL(first_block_node(0x800001), 1023); +} + +TORRENT_TEST(mismatching_file_hash1) +{ + file_storage st; + st.set_piece_length(0x4000); + + error_code ec; + st.add_file(ec, combine_path("test", "a"), 10000); + TEST_CHECK(!ec); + st.add_file(ec, combine_path("test", "B"), 10000, {}, 0, {}, "abababababababababababababababab"); + TEST_CHECK(ec); +} + +TORRENT_TEST(mismatching_file_hash2) +{ + file_storage st; + st.set_piece_length(0x4000); + + error_code ec; + st.add_file(ec, combine_path("test", "B"), 10000, {}, 0, {}, "abababababababababababababababab"); + TEST_CHECK(!ec); + st.add_file(ec, combine_path("test", "a"), 10000); + TEST_CHECK(ec); +} + +TORRENT_TEST(v2_detection_1) +{ + file_storage fs; + fs.set_piece_length(0x8000); + // passing in a root hash (the last argument) makes it follow v2 rules, to + // add pad files + fs.add_file("test/0", 0x5000, {}, 0, "symlink-test-1"); + fs.add_file("test/1", 0x5000, {}, 0, "symlink-test-2"); + + fs.add_file("test/2", 0x2000, {}, 0, {}, "01234567890123456789012345678901"); + // it's an error to add a v1 file to a v2 torrent + TEST_THROW(fs.add_file("test/3", 0x2000)); +} + +TORRENT_TEST(v2_detection_2) +{ + file_storage fs; + fs.set_piece_length(0x8000); + // passing in a root hash (the last argument) makes it follow v2 rules, to + // add pad files + fs.add_file("test/0", 0x5000, {}, 0, "symlink-test-1"); + fs.add_file("test/1", 0x5000, {}, 0, "symlink-test-2"); + + fs.add_file("test/2", 0x2000); + + // it's an error to add a v1 file to a v2 torrent + TEST_THROW(fs.add_file("test/3", 0x2000, {}, 0, {}, "01234567890123456789012345678901")); +} + +TORRENT_TEST(blocks_in_piece2) +{ + static std::map const piece_sizes = { + {0x3fff, 1}, + {0x4000, 1}, + {0x4001, 2}, + }; + + for (auto t : piece_sizes) + { + file_storage fs; + fs.set_piece_length(0x8000); + fs.add_file("test/0", t.first, {}, 0, {}, "01234567890123456789012345678901"); + fs.set_num_pieces(aux::calc_num_pieces(fs)); + TEST_EQUAL(fs.blocks_in_piece2(0_piece), t.second); + } +} + +TORRENT_TEST(file_index_for_root) +{ + file_storage fs; + fs.set_piece_length(0x8000); + fs.add_file("test/0", 0x8000, {}, 0, {}, "11111111111111111111111111111111"); + fs.add_file("test/1", 0x8000, {}, 0, {}, "22222222222222222222222222222222"); + fs.add_file("test/2", 0x8000, {}, 0, {}, "33333333333333333333333333333333"); + fs.add_file("test/3", 0x8000, {}, 0, {}, "44444444444444444444444444444444"); + + TEST_EQUAL(fs.file_index_for_root(sha256_hash("11111111111111111111111111111111")), file_index_t{0}); + TEST_EQUAL(fs.file_index_for_root(sha256_hash("22222222222222222222222222222222")), file_index_t{1}); + TEST_EQUAL(fs.file_index_for_root(sha256_hash("33333333333333333333333333333333")), file_index_t{2}); + TEST_EQUAL(fs.file_index_for_root(sha256_hash("44444444444444444444444444444444")), file_index_t{3}); + TEST_EQUAL(fs.file_index_for_root(sha256_hash("55555555555555555555555555555555")), file_index_t{-1}); +} + +TORRENT_TEST(size_on_disk_explicit_pads) +{ + file_storage fs; + fs.set_piece_length(0x8000); + + std::int64_t size_on_disk = 0; + TEST_EQUAL(aux::size_on_disk(fs), size_on_disk); + fs.add_file("test/0", 100, {}, 0, {}, "11111111111111111111111111111111"); + size_on_disk += 100; + TEST_EQUAL(aux::size_on_disk(fs), size_on_disk); + + // when adding a pad file, size_on_disk does not increment + fs.add_file("test/pad/0", 80, file_storage::flag_pad_file, 0, {}, "22222222222222222222222222222222"); + TEST_EQUAL(aux::size_on_disk(fs), size_on_disk); + fs.add_file("test/2", 333, {}, 0, {}, "33333333333333333333333333333333"); + size_on_disk += 333; + TEST_EQUAL(aux::size_on_disk(fs), size_on_disk); + TEST_CHECK(aux::size_on_disk(fs) < fs.total_size()); +} + +// TODO: test file attributes +// TODO: test symlinks diff --git a/test/test_flags.cpp b/test/test_flags.cpp new file mode 100644 index 0000000..cb27b2d --- /dev/null +++ b/test/test_flags.cpp @@ -0,0 +1,235 @@ +/* + +Copyright (c) 2017, 2019-2020, Arvid Norberg +Copyright (c) 2017, AllSeeingEyeTolledEweSew +Copyright (c) 2018, 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/add_torrent_params.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/aux_/path.hpp" +#include "settings.hpp" +#include "setup_transfer.hpp" +#include "test_utils.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 print_alerts(lt::session& ses) +{ + std::vector alerts; + ses.pop_alerts(&alerts); + for (auto a : alerts) + { + std::printf("[%s] %s\n", a->what(), a->message().c_str()); + } +} + +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)); + if (flags & torrent_flags::seed_mode) + { + std::vector temp(425); + ofstream("temp").write(temp.data(), std::streamsize(temp.size())); + } + 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); + print_alerts(ses); +} + +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); + print_alerts(ses); +} + +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{}); + print_alerts(ses); +} + +} // 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); +} + +// super seeding mode is automatically turned off if we're not a seed +// since the posix_disk_io is not threaded, this will happen immediately +#if TORRENT_HAVE_MMAP +#ifndef TORRENT_DISABLE_SUPERSEEDING +TORRENT_TEST(flag_super_seeding) +{ + // super-seeding + test_add_and_get_flags(torrent_flags::super_seeding); + test_unset_after_add(torrent_flags::super_seeding); + test_set_after_add(torrent_flags::super_seeding); +} +#endif +#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); +} + +// the stop when ready flag will be cleared when the torrent is ready to start +// downloading. +// since the posix_disk_io is not threaded, this will happen immediately +#if TORRENT_HAVE_MMAP +TORRENT_TEST(flag_stop_when_ready) +{ + // stop-when-ready + // TODO: this test is flaky, since the torrent will become ready before + // asking for the flags, and by then stop_when_ready will have been cleared + //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); +} +#endif + +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..2106e0d --- /dev/null +++ b/test/test_generate_peer_id.cpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2018-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 "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..8f25e79 --- /dev/null +++ b/test/test_gzip.cpp @@ -0,0 +1,101 @@ +/* + +Copyright (c) 2014, 2016-2017, 2019, Arvid Norberg +Copyright (c) 2018, 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/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_hash_picker.cpp b/test/test_hash_picker.cpp new file mode 100644 index 0000000..a52eb8b --- /dev/null +++ b/test/test_hash_picker.cpp @@ -0,0 +1,626 @@ +/* + +Copyright (c) 2017, BitTorrent Inc. +Copyright (c) 2019-2020, Steven Siloti +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/hash_picker.hpp" +#include "libtorrent/peer_connection_interface.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/aux_/merkle.hpp" +#include "libtorrent/hex.hpp" +#include "libtorrent/disk_interface.hpp" // for default_block_size + +#include "test.hpp" +#include "test_utils.hpp" + +using namespace lt; + +struct mock_peer_connection final : peer_connection_interface +{ + tcp::endpoint const& remote() const override { return m_remote; } + tcp::endpoint local_endpoint() const override { return {}; } + void disconnect(error_code const&, operation_t, disconnect_severity_t) override {} + peer_id const& pid() const override { return m_pid; } + peer_id our_pid() const override { return m_pid; } + void set_holepunch_mode() override {} + torrent_peer* peer_info_struct() const override { return m_torrent_peer; } + void set_peer_info(torrent_peer* pi) override { m_torrent_peer = pi; } + bool is_outgoing() const override { return false; } + void add_stat(std::int64_t, std::int64_t) override {} + bool fast_reconnect() const override { return false; } + bool is_choked() const override { return false; } + bool failed() const override { return false; } + lt::stat const& statistics() const override { return m_stat; } + void get_peer_info(peer_info&) const override {} +#ifndef TORRENT_DISABLE_LOGGING + bool should_log(peer_log_alert::direction_t) const override { return true; } + void peer_log(peer_log_alert::direction_t + , char const*, char const*, ...) const noexcept override TORRENT_FORMAT(4, 5) {} +#endif + + torrent_peer* m_torrent_peer; + lt::stat m_stat; + tcp::endpoint m_remote; + peer_id m_pid; +}; + +#if 0 +TORRENT_TEST(pick_piece_layer) +{ + file_storage fs; + fs.set_piece_length(16 * 1024); + + fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); + fs.add_file("test/tmp2", 4 * 512 * 16 * 1024); + + aux::vector trees; + + trees.push_back(aux::merkle_tree(merkle_num_nodes(merkle_num_leafs(4 * 512)))); + aux::from_hex("0000000000000000000000000000000000000000000000000000000000000001", trees.back()[0].data()); + trees.push_back(aux::merkle_tree(merkle_num_nodes(merkle_num_leafs(4 * 512)))); + aux::from_hex("0000000000000000000000000000000000000000000000000000000000000001", trees.back()[0].data()); + + hash_picker picker(fs, trees); + + typed_bitfield pieces; + pieces.resize(8 * 512); + pieces.set_all(); + + mock_peer_connection mock_peer1, mock_peer2; + mock_peer1.m_torrent_peer = (torrent_peer*)0x1; + mock_peer2.m_torrent_peer = (torrent_peer*)0x2; + + auto picked = picker.pick_hashes(pieces, 2, &mock_peer1); + TEST_EQUAL(int(picked.size()), 2); + TEST_EQUAL(picked[0].file, 0_file); + TEST_EQUAL(picked[0].base, 0); + TEST_EQUAL(picked[0].count, 512); + TEST_EQUAL(picked[0].index, 0); + TEST_EQUAL(picked[0].proof_layers, 10); + TEST_EQUAL(picked[1].file, 0_file); + TEST_EQUAL(picked[1].base, 0); + TEST_EQUAL(picked[1].count, 512); + TEST_EQUAL(picked[1].index, 512); + TEST_EQUAL(picked[1].proof_layers, 10); + + picked = picker.pick_hashes(pieces, 3, &mock_peer2); + TEST_EQUAL(int(picked.size()), 3); + TEST_EQUAL(picked[0].file, 0_file); + TEST_EQUAL(picked[0].base, 0); + TEST_EQUAL(picked[0].count, 512); + TEST_EQUAL(picked[0].index, 1024); + TEST_EQUAL(picked[0].proof_layers, 10); + TEST_EQUAL(picked[1].file, 0_file); + TEST_EQUAL(picked[1].base, 0); + TEST_EQUAL(picked[1].count, 512); + TEST_EQUAL(picked[1].index, 1536); + TEST_EQUAL(picked[1].proof_layers, 10); + TEST_EQUAL(picked[2].file, 1_file); + TEST_EQUAL(picked[2].base, 0); + TEST_EQUAL(picked[2].count, 512); + TEST_EQUAL(picked[2].index, 0); + TEST_EQUAL(picked[2].proof_layers, 10); + + picked = picker.pick_hashes(pieces, 4, &mock_peer1); + TEST_EQUAL(int(picked.size()), 3); + TEST_EQUAL(picked[0].file, 1_file); + TEST_EQUAL(picked[0].base, 0); + TEST_EQUAL(picked[0].count, 512); + TEST_EQUAL(picked[0].index, 512); + TEST_EQUAL(picked[0].proof_layers, 10); + TEST_EQUAL(picked[1].file, 1_file); + TEST_EQUAL(picked[1].base, 0); + TEST_EQUAL(picked[1].count, 512); + TEST_EQUAL(picked[1].index, 1024); + TEST_EQUAL(picked[1].proof_layers, 10); + TEST_EQUAL(picked[2].file, 1_file); + TEST_EQUAL(picked[2].base, 0); + TEST_EQUAL(picked[2].count, 512); + TEST_EQUAL(picked[2].index, 1536); + TEST_EQUAL(picked[2].proof_layers, 10); +} +#endif + +namespace { +sha256_hash from_hex(span str) +{ + sha256_hash ret; + aux::from_hex(str, ret.data()); + return ret; +} +} + +TORRENT_TEST(reject_piece_request) +{ + file_storage fs; + fs.set_piece_length(16 * 1024); + + fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); + + aux::vector trees; + auto const root = from_hex("0000000000000000000000000000000000000000000000000000000000000001"); + trees.emplace_back(4 * 512, 1, root.data()); + + hash_picker picker(fs, trees); + + typed_bitfield const pieces(4 * 512, true); + + auto const picked = picker.pick_hashes(pieces); + picker.hashes_rejected(picked); + + auto const picked2 = picker.pick_hashes(pieces); + TEST_CHECK(picked == picked2); +} + +TORRENT_TEST(add_leaf_hashes) +{ + file_storage fs; + fs.set_piece_length(16 * 1024); + + fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); + + aux::vector trees; + auto const full_tree = build_tree(4 * 512); + sha256_hash const root = full_tree[0]; + trees.emplace_back(4 * 512, 1, root.data()); + + hash_picker picker(fs, trees); + + std::vector hashes; + auto const pieces_start = full_tree.end_index() - merkle_num_leafs(4 * 512); + for (int i = 0; i < 512; ++i) hashes.push_back(full_tree[pieces_start + i]); + for (int i = 3; i > 0; i = merkle_get_parent(i)) + { + hashes.push_back(full_tree[merkle_get_sibling(i)]); + } + add_hashes_result result = picker.add_hashes(hash_request(0_file, 0, 0, 512, 10) + , hashes); + TEST_CHECK(result.valid); + + result = picker.add_hashes(hash_request(0_file, 0, 512, 512, 0) + , span(full_tree).last(merkle_num_leafs(4 * 512) - 512).first(512)); + TEST_CHECK(result.valid); + + hashes.clear(); + for (int i = 1024; i < 1536; ++i) hashes.push_back(full_tree[pieces_start + i]); + for (int i = 5; i > 0; i = merkle_get_parent(i)) + { + hashes.push_back(full_tree[merkle_get_sibling(i)]); + } + + result = picker.add_hashes(hash_request(0_file, 0, 1024, 512, 10) + , hashes); + TEST_CHECK(result.valid); + + result = picker.add_hashes(hash_request(0_file, 0, 1536, 512, 0) + , span(full_tree).last(merkle_num_leafs(4 * 512) - 1536).first(512)); + TEST_CHECK(result.valid); + + TEST_CHECK(trees.front().build_vector() == full_tree); +} + +TORRENT_TEST(add_piece_hashes) +{ + file_storage fs; + fs.set_piece_length(4 * 16 * 1024); + + fs.add_file("test/tmp1", 4 * 1024 * 16 * 1024); + + aux::vector trees; + auto const full_tree = build_tree(4 * 1024); + sha256_hash const root = full_tree[0]; + trees.emplace_back(4 * 1024, 4, root.data()); + + hash_picker picker(fs, trees); + + auto pieces_start = full_tree.begin() + merkle_num_nodes(1024) - 1024; + + std::vector hashes; + std::copy(pieces_start, pieces_start + 512, std::back_inserter(hashes)); + hashes.push_back(full_tree[2]); + add_hashes_result result = picker.add_hashes(hash_request(0_file, 2, 0, 512, 9), hashes); + TEST_CHECK(result.valid); + + hashes.clear(); + std::copy(pieces_start + 512, pieces_start + 1024, std::back_inserter(hashes)); + result = picker.add_hashes(hash_request(0_file, 2, 512, 512, 8), hashes); + TEST_CHECK(result.valid); + + auto const cmp = trees.front().build_vector(); + TEST_CHECK(std::equal(cmp.begin(), cmp.begin() + merkle_num_nodes(1024), full_tree.begin())); +} + +TORRENT_TEST(add_piece_hashes_padded) +{ + file_storage fs; + fs.set_piece_length(4 * 16 * 1024); + + fs.add_file("test/tmp1", 4 * 1029 * 16 * 1024); + + aux::vector trees; + auto const full_tree = build_tree(4 * 1029); + sha256_hash const root = full_tree[0]; + trees.emplace_back(4 * 1029, 4, root.data()); + + hash_picker picker(fs, trees); + + auto pieces_start = merkle_num_nodes(merkle_num_leafs(1029)) - merkle_num_leafs(1029); + + std::vector hashes; + // 5 hashes left after 1024 rounds up to 8, 1024 + 8 = 1032 + std::copy(full_tree.begin() + pieces_start + 1024, full_tree.begin() + pieces_start + 1032 + , std::back_inserter(hashes)); + auto proof = merkle_get_parent(merkle_get_parent(merkle_get_parent(pieces_start + 1024))); + while (proof > 0) + { + hashes.push_back(full_tree[merkle_get_sibling(proof)]); + proof = merkle_get_parent(proof); + } + add_hashes_result result = picker.add_hashes(hash_request(0_file, 2, 1024, 8, 10), hashes); + TEST_CHECK(result.valid); +} + +TORRENT_TEST(add_piece_hashes_unpadded) +{ + file_storage fs; + fs.set_piece_length(4 * 16 * 1024); + + fs.add_file("test/tmp1", 4 * 1029 * 16 * 1024); + + aux::vector trees; + auto const full_tree = build_tree(4 * 1029); + sha256_hash const root = full_tree[0]; + trees.emplace_back(4 * 1029, 4, root.data()); + + hash_picker picker(fs, trees); + + auto pieces_start = merkle_num_nodes(merkle_num_leafs(1029)) - merkle_num_leafs(1029); + + std::vector hashes; + std::copy(full_tree.begin() + pieces_start + 1024, full_tree.begin() + pieces_start + 1029 + , std::back_inserter(hashes)); + auto proof = merkle_get_parent(merkle_get_parent(merkle_get_parent(pieces_start + 1024))); + while (proof > 0) + { + hashes.push_back(full_tree[merkle_get_sibling(proof)]); + proof = merkle_get_parent(proof); + } + add_hashes_result result = picker.add_hashes(hash_request(0_file, 2, 1024, 5, 10), hashes); + TEST_CHECK(result.valid); +} + +TORRENT_TEST(add_bad_hashes) +{ + file_storage fs; + fs.set_piece_length(4 * 16 * 1024); + + fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); + + aux::vector trees; + auto const full_tree = build_tree(4 * 512); + sha256_hash const root = full_tree[0]; + trees.emplace_back(4 * 512, 4, root.data()); + + hash_picker picker(fs, trees); + + // totally bogus hashes + std::vector hashes(512); + auto result = picker.add_hashes(hash_request(0_file, 2, 0, 512, 0), hashes); + TEST_CHECK(!result.valid); + + // bad proof hash + hashes.clear(); + auto const pieces_start = full_tree.end_index() - 512; + for (int i = 0; i < 512; ++i) hashes.push_back(full_tree[pieces_start + i]); + hashes.back()[1] ^= 0xaa; + result = picker.add_hashes(hash_request(0_file, 2, 0, 512, 0), hashes); + TEST_CHECK(!result.valid); +} + +TORRENT_TEST(bad_block_hash) +{ + file_storage fs; + fs.set_piece_length(16 * 1024); + + fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); + + auto const full_tree = build_tree(4 * 512); + + aux::vector trees; + trees.emplace_back(4 * 512, 1, full_tree[0].data()); + + sha256_hash hash; + aux::from_hex("0000000000000000000000000000000000000000000000000000000000000001" + , hash.data()); + + trees.front().set_block(1, hash); + + hash_picker picker(fs, trees); + + std::vector hashes; + auto leafs_start = full_tree.end() - merkle_num_leafs(4 * 512); + std::copy(leafs_start, leafs_start + 512, std::back_inserter(hashes)); + for (int i = 3; i > 0; i = merkle_get_parent(i)) + { + hashes.push_back(full_tree[merkle_get_sibling(i)]); + } + add_hashes_result result = picker.add_hashes(hash_request(0_file, 0, 0, 512, 10) + , hashes); + TEST_CHECK(result.valid); + TEST_CHECK((result.hash_failed == std::vector>>{{1_piece, {0}}})); +} + +TORRENT_TEST(set_block_hash) +{ + file_storage fs; + fs.set_piece_length(4 * 16 * 1024); + + fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); + + aux::vector trees; + auto const full_tree = build_tree(4 * 512); + trees.emplace_back(4 * 512, 4, full_tree[0].data()); + trees.front().load_tree(full_tree, std::vector(std::size_t(merkle_num_leafs(4 * 512)), false)); + + int const first_leaf = full_tree.end_index() - merkle_num_leafs(4 * 512); + + hash_picker picker(fs, trees); + auto result = picker.set_block_hash(1_piece, default_block_size + , full_tree[first_leaf + 5]); + TEST_CHECK(result.status == set_block_hash_result::result::success); + + result = picker.set_block_hash(2_piece, default_block_size * 2 + , full_tree[first_leaf + 10]); + TEST_CHECK(result.status == set_block_hash_result::result::success); + + result = picker.set_block_hash(2_piece, default_block_size * 2 + , sha256_hash("01234567890123456789012345678901")); + TEST_CHECK(result.status == set_block_hash_result::result::block_hash_failed); +} + +TORRENT_TEST(set_block_hash_fail) +{ + file_storage fs; + fs.set_piece_length(4 * 16 * 1024); + + fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); + + aux::vector trees; + auto full_tree = build_tree(4 * 512); + trees.emplace_back(4 * 512, 4, full_tree[0].data()); + + // zero out the inner nodes for a piece along with a single leaf node + // then add a bogus hash for the leaf + + int const first_leaf = full_tree.end_index() - merkle_num_leafs(4 * 512); + + full_tree[merkle_get_parent(first_leaf + 12)].clear(); + full_tree[merkle_get_parent(first_leaf + 14)].clear(); + full_tree[first_leaf + 13].clear(); + + trees.front().load_tree(full_tree, std::vector(std::size_t(merkle_num_leafs(4 * 512)), false)); + + hash_picker picker(fs, trees); + + TEST_CHECK(picker.set_block_hash(3_piece, 0, full_tree[first_leaf + 12]).status + == lt::set_block_hash_result::result::unknown); + TEST_CHECK(picker.set_block_hash(3_piece, 2 * default_block_size, full_tree[first_leaf + 14]).status + == lt::set_block_hash_result::result::unknown); + TEST_CHECK(picker.set_block_hash(3_piece, 3 * default_block_size, full_tree[first_leaf + 15]).status + == lt::set_block_hash_result::result::unknown); + + auto const result = picker.set_block_hash(3_piece, default_block_size, sha256_hash("01234567890123456789012345678901")); + TEST_CHECK(result.status == set_block_hash_result::result::piece_hash_failed); + + TEST_CHECK(trees.front()[merkle_get_parent(first_leaf + 12)].is_all_zeros()); + TEST_CHECK(trees.front()[merkle_get_parent(first_leaf + 13)].is_all_zeros()); + TEST_CHECK(trees.front()[merkle_get_parent(first_leaf + 14)].is_all_zeros()); + TEST_CHECK(trees.front()[merkle_get_parent(first_leaf + 15)].is_all_zeros()); +} + +TORRENT_TEST(set_block_hash_pass) +{ + file_storage fs; + fs.set_piece_length(4 * 16 * 1024); + + fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); + + aux::vector trees; + auto full_tree = build_tree(4 * 512); + trees.emplace_back(4 * 512, 4, full_tree[0].data()); + + // zero out the inner nodes for a piece along with a single leaf node + // then add a bogus hash for the leaf + + int const first_leaf = full_tree.end_index() - merkle_num_leafs(4 * 512); + + full_tree[merkle_get_parent(first_leaf + 12)].clear(); + full_tree[merkle_get_parent(first_leaf + 14)].clear(); + auto const orig_hash = full_tree[first_leaf + 13]; + full_tree[first_leaf + 13].clear(); + + trees.front().load_tree(full_tree, std::vector(std::size_t(merkle_num_leafs(4 * 512)), false)); + + hash_picker picker(fs, trees); + + TEST_CHECK(picker.set_block_hash(3_piece, 0, full_tree[first_leaf + 12]).status + == lt::set_block_hash_result::result::unknown); + TEST_CHECK(picker.set_block_hash(3_piece, 2 * default_block_size, full_tree[first_leaf + 14]).status + == lt::set_block_hash_result::result::unknown); + TEST_CHECK(picker.set_block_hash(3_piece, 3 * default_block_size, full_tree[first_leaf + 15]).status + == lt::set_block_hash_result::result::unknown); + + auto const result = picker.set_block_hash(3_piece, default_block_size, orig_hash); + TEST_CHECK(result.status == set_block_hash_result::result::success); +} + +TORRENT_TEST(pass_piece) +{ + file_storage fs; + fs.set_piece_length(4 * 16 * 1024); + + fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); + + auto const full_tree = build_tree(4 * 512); + + aux::vector trees; + sha256_hash root = full_tree[0]; + trees.emplace_back(4 * 512, 4, root.data()); + + hash_picker picker(fs, trees); + + int const first_leaf = full_tree.end_index() - merkle_num_leafs(4 * 512); + + for (int i = 0; i < 4; ++i) + { + auto result = picker.set_block_hash(0_piece, default_block_size * i + , full_tree[first_leaf + i]); + TEST_CHECK(result.status == set_block_hash_result::result::unknown); + } + + auto const pieces_start = full_tree.begin() + merkle_num_nodes(512) - 512; + + std::vector hashes; + std::copy(pieces_start, pieces_start + 512, std::back_inserter(hashes)); + add_hashes_result result = picker.add_hashes(hash_request(0_file, 2, 0, 512, 8), hashes); + TEST_CHECK(result.valid); + TEST_EQUAL(result.hash_passed.size(), 1); + if (result.hash_passed.size() == 1) + { + TEST_EQUAL(result.hash_passed[0], 0_piece); + } +} + +TORRENT_TEST(only_pick_have_pieces) +{ + file_storage fs; + fs.set_piece_length(16 * 1024); + + fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); + + aux::vector trees; + sha256_hash root = from_hex("0000000000000000000000000000000000000000000000000000000000000001"); + trees.emplace_back(4 * 512, 1, root.data()); + + hash_picker picker(fs, trees); + + typed_bitfield pieces; + pieces.resize(4 * 512); + pieces.set_bit(512_piece); + pieces.set_bit(1537_piece); + + std::vector picked; + for (int i = 0; i < 3; ++i) + picked.push_back(picker.pick_hashes(pieces)); + TEST_EQUAL(picked[0].file, 0_file); + TEST_EQUAL(picked[0].base, 0); + TEST_EQUAL(picked[0].count, 512); + TEST_EQUAL(picked[0].index, 512); + TEST_EQUAL(picked[0].proof_layers, 10); + TEST_EQUAL(picked[1].file, 0_file); + TEST_EQUAL(picked[1].base, 0); + TEST_EQUAL(picked[1].count, 512); + TEST_EQUAL(picked[1].index, 1536); + TEST_EQUAL(picked[1].proof_layers, 10); + TEST_EQUAL(picked[2].count, 0); +} + +TORRENT_TEST(validate_hash_request) +{ + file_storage fs; + fs.set_piece_length(16 * 1024); + fs.add_file("test/tmp1", 2048 * 16 * 1024); + + // the merkle tree for this file has 2048 blocks + int const num_leaves = merkle_num_leafs(2048); + int const num_layers = merkle_num_layers(num_leaves); + + int const max = std::numeric_limits::max(); + int const min = std::numeric_limits::min(); + + // hash_request make function + // (file_index_t const f, int const b, int const i, int const c, int const p) + + TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 0, 0, 1, 0), fs)); + + // file index out-of-range + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{1}, 0, 0, 1, 0), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{-1}, 0, 0, 1, 0), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{max}, 0, 0, 1, 0), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{min}, 0, 0, 1, 0), fs)); + + // base out-of-range + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, -1, 0, 1, 0), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, num_layers, 0, 1, 0), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, max, 0, 1, 0), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, min, 0, 1, 0), fs)); + + // base in-range + TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 0, 0, 1, 0), fs)); + TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, num_layers-1, 0, 1, 0), fs)); + + // count out-of-range + // the upper limit of count depends on base and index + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 0, 0, 0, 0), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 0, 0, num_leaves + 1, 0), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 0, 100, num_leaves - 100 + 1, 0), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 0, 0, 8193, 0), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 0, 0, min, 0), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 0, 0, max, 0), fs)); + + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 1, 0, num_leaves / 2 + 1, 0), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 2, 0, num_leaves / 4 + 1, 0), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 3, 0, num_leaves / 8 + 1, 0), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 3, 5, num_leaves / 8 - 5 + 1, 0), fs)); + + // count in-range + TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 0, 100, num_leaves - 100, 0), fs)); + TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 0, 0, 1, 0), fs)); + TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 0, 0, num_leaves, 0), fs)); + + TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 1, 0, num_leaves / 2, 0), fs)); + TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 2, 0, num_leaves / 4, 0), fs)); + TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 3, 0, num_leaves / 8, 0), fs)); + TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 3, 5, num_leaves / 8 - 5, 0), fs)); + + // proof_layers out-of-range + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 0, 0, 1, num_layers), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 1, 0, 1, num_layers), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 1, 0, 1, min), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 1, 0, 1, max), fs)); + TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 1, 0, 1, -1), fs)); + + // proof_layers in-range + TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 0, 0, 1, num_layers - 1), fs)); + TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 1, 0, 1, num_layers - 2), fs)); +} + diff --git a/test/test_hasher.cpp b/test/test_hasher.cpp new file mode 100644 index 0000000..b38dd4d --- /dev/null +++ b/test/test_hasher.cpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2005, 2008-2009, 2015, 2017-2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2016, Pavel Pimenov +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/hasher.hpp" +#include "libtorrent/hex.hpp" + +#include "test.hpp" + +#include + +using namespace lt; + +namespace +{ +// test vectors from RFC 3174 +// http://www.faqs.org/rfcs/rfc3174.html + +struct test_vector_t +{ + string_view input; + int repetitions; + string_view hex_output; +}; + +std::array sha1_vectors = {{ + {"abc", 1, "A9993E364706816ABA3E25717850C26C9CD0D89D"}, + {"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 1, "84983E441C3BD26EBAAE4AA1F95129E5E54670F1"}, + {"a", 1000000, "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F"}, + {"0123456701234567012345670123456701234567012345670123456701234567", 10, "DEA356A2CDDD90C7A7ECEDC5EBB563934F460452"} +}}; + +// https://www.dlitz.net/crypto/shad256-test-vectors/ +std::array sha256_vectors = {{ + {"abc", 1, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"}, + {"\xde\x18\x89\x41\xa3\x37\x5d\x3a\x8a\x06\x1e\x67\x57\x6e\x92\x6d", 1, "067c531269735ca7f541fdaca8f0dc76305d3cada140f89372a410fe5eff6e4d"}, + {"a", 1000000, "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"}, +}}; + +void test_vector(string_view s, string_view 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 (auto const& t : sha1_vectors) + { + hasher h; + for (int i = 0; i < t.repetitions; ++i) + h.update(t.input.data(), int(t.input.size())); + + sha1_hash result; + aux::from_hex(t.hex_output, 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 + ); +} + +TORRENT_TEST(hasher256) +{ + for (auto const& t : sha256_vectors) + { + hasher256 h; + for (int i = 0; i < t.repetitions; ++i) + h.update(t.input.data(), int(t.input.size())); + + TEST_EQUAL(t.hex_output, aux::to_hex(h.final())); + } +} + diff --git a/test/test_hasher512.cpp b/test/test_hasher512.cpp new file mode 100644 index 0000000..ae852c5 --- /dev/null +++ b/test/test_hasher512.cpp @@ -0,0 +1,92 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, 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_DISABLE_DHT + +#include "libtorrent/aux_/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) + { + aux::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..7f97860 --- /dev/null +++ b/test/test_heterogeneous_queue.cpp @@ -0,0 +1,339 @@ +/* + +Copyright (c) 2015-2017, 2019, Arvid Norberg +Copyright (c) 2018, 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/aux_/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; + } + + // non-copyable + F& operator=(F const& f) = delete; + + 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; +}; + +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..93745d4 --- /dev/null +++ b/test/test_http_connection.cpp @@ -0,0 +1,250 @@ +/* + +Copyright (c) 2016, Steven Siloti +Copyright (c) 2007-2010, 2013-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/resolver.hpp" +#include "libtorrent/aux_/storage_utils.hpp" +#include "libtorrent/random.hpp" + +#include +#include + +using namespace lt; + +namespace { + +io_context ios; +aux::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() == make_address("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; + +#if TORRENT_USE_SSL + 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() + , hostname_filter_handler() +#if TORRENT_USE_SSL + , &ssl_ctx +#endif + ); + h->get(url, seconds(5), 0, &ps, 5, "test/user-agent", boost::none, aux::resolver_flags{}, auth); + ios.restart(); + ios.run(); + + 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); +} + +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) +{ + aux::random_bytes(data_buffer); + ofstream("test_file").write(data_buffer, 3216); + + // 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 + +#if TORRENT_USE_SSL +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_SSL + +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..3f4e13d --- /dev/null +++ b/test/test_http_parser.cpp @@ -0,0 +1,914 @@ +/* + +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2018, 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/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")); + + // if there is no path component, "/" is added + TEST_CHECK(parse_url_components("http://test.com:42", ec) + == std::make_tuple("http", "", "test.com", 42, "/")); + + TEST_CHECK(parse_url_components("http://test.com:42/", ec) + == std::make_tuple("http", "", "test.com", 42, "/")); + + TEST_CHECK(parse_url_components("http://test.com:42?query=string", ec) + == std::make_tuple("http", "", "test.com", 42, "/?query=string")); + + TEST_CHECK(parse_url_components("http://test.com:42/?query=string", ec) + == std::make_tuple("http", "", "test.com", 42, "/?query=string")); + + TEST_CHECK(parse_url_components("http://test.com:42#fragment", ec) + == std::make_tuple("http", "", "test.com", 42, "/#fragment")); + + TEST_CHECK(parse_url_components("http://test.com:42/#fragment", ec) + == std::make_tuple("http", "", "test.com", 42, "/#fragment")); + + // 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(); + + parse_url_components("http://test.com:42abc", ec); + TEST_CHECK(ec == error_code(errors::invalid_port)); + + // 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); +} + +namespace { +std::string test_collapse_chunks(std::string chunked_input, bool const expect_error = false) +{ + http_parser parser; + + std::string input = "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: text/plain\r\n" + "\r\n"; + + input += chunked_input; + + bool error = false; + int payload = 0; + int protocol = 0; + std::tie(payload, protocol) = parser.incoming(input, error); + + TEST_CHECK(protocol > 0); + TEST_CHECK(payload > 0); + if (expect_error) + { + TEST_CHECK(std::size_t(protocol + payload) <= input.size()); + } + else + { + TEST_EQUAL(std::size_t(protocol + payload), input.size()); + TEST_CHECK(parser.finished()); + } + + std::vector mutable_buffer; + span body = parser.get_body(); + std::copy(body.begin(), body.end(), std::back_inserter(mutable_buffer)); + body = parser.collapse_chunk_headers(mutable_buffer); + return std::string(body.data(), std::size_t(body.size())); +} +} + +TORRENT_TEST(chunked_encoding) +{ + auto const collapsed = test_collapse_chunks( + "4\r\ntest\r\n4\r\n1234\r\n10\r\n0123456789abcdef\r\n" + "0\r\n\r\n"); + TEST_EQUAL(collapsed, "test12340123456789abcdef"); +} + +TORRENT_TEST(chunked_encoding_beyond_end) +{ + auto const collapsed = test_collapse_chunks( + "4\r\ntest\r\n4\r\n1234\r\n20\r\n0123456789abcdef\r\n" + "0\r\n\r\n", true); + TEST_EQUAL(collapsed, "test1234"); +} + +TORRENT_TEST(chunked_encoding_end_of_buffer) +{ + auto const collapsed = test_collapse_chunks( + "4\r\ntest\r\n4\r\n1234\r\n17\r\n0123456789abcdef\r\n" + "0\r\n\r\n", true); + TEST_EQUAL(collapsed, "test12340123456789abcdef\r\n0\r\n\r\n"); +} + +TORRENT_TEST(chunked_encoding_past_end) +{ + auto const collapsed = test_collapse_chunks( + "4\r\ntest\r\n4\r\n1234\r\n18\r\n0123456789abcdef\r\n" + "0\r\n\r\n", true); + TEST_EQUAL(collapsed, "test1234"); +} + +TORRENT_TEST(chunked_encoding_negative) +{ + auto const collapsed = test_collapse_chunks( + "4\r\ntest\r\n4\r\n1234\r\n-10\r\n0123456789abcdef\r\n" + "0\r\n\r\n", true); + TEST_EQUAL(collapsed, ""); +} + +TORRENT_TEST(chunked_encoding_end) +{ + auto const collapsed = test_collapse_chunks( + "4\r\ntest\r\n4\r\n1234\r\n11\r\n0123456789abcdef\r\n" + "0\r\n\r\n", true); + TEST_EQUAL(collapsed, "test12340123456789abcdef\r"); +} + +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)}); +} + +TORRENT_TEST(idna) +{ + TEST_CHECK(!is_idna("a.b.com")); + TEST_CHECK(!is_idna("example.com")); + TEST_CHECK(!is_idna("exn--ample.com")); + + // xn-- is the ACE introducer for a punycoded label. It can appear at the + // start of any label, but not in the middle (if it does, it's not + // interpreted as an ACE). Since hostnames are case insensitive, the ACE + // introducer has to be as well + TEST_CHECK(is_idna("xn--example.com")); + TEST_CHECK(is_idna("xN--example.com")); + TEST_CHECK(is_idna("xn--example-.com")); + TEST_CHECK(is_idna("subdomain.xn--example-.com")); + TEST_CHECK(is_idna("subdomain.example.xn--com-")); + + // this isn't valid IDNA, but it's suspicious + TEST_CHECK(is_idna("xn--.com")); + + // some weird edge-cases + TEST_CHECK(!is_idna("..............")); + TEST_CHECK(is_idna(".....xn--.........")); + TEST_CHECK(is_idna(".....Xn--.........")); + TEST_CHECK(is_idna(".....xN--.........")); + TEST_CHECK(is_idna(".....XN--.........")); + TEST_CHECK(!is_idna(".....-xn--.........")); + TEST_CHECK(is_idna("xn--")); + TEST_CHECK(is_idna("Xn--")); + TEST_CHECK(is_idna("XN--")); + TEST_CHECK(is_idna("xN--")); + TEST_CHECK(is_idna("xn--.xn--")); + TEST_CHECK(is_idna(".....xn--")); + TEST_CHECK(is_idna("xn--...")); + TEST_CHECK(is_idna("xN--...")); + TEST_CHECK(!is_idna("")); + TEST_CHECK(!is_idna(".")); + + TEST_CHECK(!is_idna("x")); + TEST_CHECK(!is_idna("xn")); + TEST_CHECK(!is_idna("xn-")); + TEST_CHECK(!is_idna("-xn--")); + + TEST_CHECK(!is_idna(".x")); + TEST_CHECK(!is_idna(".xn")); + TEST_CHECK(!is_idna(".xn-")); + TEST_CHECK(!is_idna(".-xn--")); + + TEST_CHECK(!is_idna("x.")); + TEST_CHECK(!is_idna("xn.")); + TEST_CHECK(!is_idna("xn-.")); + TEST_CHECK(!is_idna("-xn--.")); +} + +TORRENT_TEST(has_tracker_query_string) +{ + TEST_CHECK(has_tracker_query_string("foo=bar&info_hash=abc&a=b"_sv)); + TEST_CHECK(has_tracker_query_string("info_hash=abc&a=b"_sv)); + TEST_CHECK(has_tracker_query_string("&&info_hash=abc&a=b"_sv)); + TEST_CHECK(has_tracker_query_string("foo=bar&info_hash=abc"_sv)); + + TEST_CHECK(has_tracker_query_string("foo=bar&port=abc&a=b"_sv)); + TEST_CHECK(has_tracker_query_string("foo=bar&event=abc&a=b"_sv)); + TEST_CHECK(has_tracker_query_string("foo=bar&downloaded=abc&a=b"_sv)); + TEST_CHECK(has_tracker_query_string("foo=bar&key=abc&a=b"_sv)); + TEST_CHECK(has_tracker_query_string("foo=bar&uploaded=abc&a=b"_sv)); + TEST_CHECK(has_tracker_query_string("foo=bar&corrupt=abc&a=b"_sv)); + TEST_CHECK(has_tracker_query_string("foo=bar&peer_id=abc&a=b"_sv)); + + TEST_CHECK(!has_tracker_query_string("foo=bar&a=b"_sv)); + TEST_CHECK(!has_tracker_query_string(""_sv)); + TEST_CHECK(!has_tracker_query_string("info_hash1=abc"_sv)); + TEST_CHECK(!has_tracker_query_string("&1port=abc"_sv)); +} diff --git a/test/test_identify_client.cpp b/test/test_identify_client.cpp new file mode 100644 index 0000000..a12b8cf --- /dev/null +++ b/test/test_identify_client.cpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2015, 2017, 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 "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_info_hash.cpp b/test/test_info_hash.cpp new file mode 100644 index 0000000..2489cee --- /dev/null +++ b/test/test_info_hash.cpp @@ -0,0 +1,164 @@ +/* + +Copyright (c) 2018, Steven Siloti +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 "test.hpp" +#include "libtorrent/info_hash.hpp" +#include "libtorrent/sha1_hash.hpp" + +#include + +using namespace lt; + +namespace { + sha1_hash const none1; + sha1_hash const zeroes1("00000000000000000000"); + sha1_hash const ones1("11111111111111111111"); + sha1_hash const twos1("22222222222222222222"); + + sha256_hash const none2; + sha256_hash const zeroes2("00000000000000000000000000000000"); + sha256_hash const ones2("11111111111111111111111111111111"); + sha256_hash const twos2("22222222222222222222222222222222"); +} + +TORRENT_TEST(ordering) +{ + // make sure the comparison function establishes a total order + + std::vector examples{ + {none1, none2}, + {none1, zeroes2}, + {none1, ones2}, + {none1, twos2}, + + {zeroes1, none2}, + {zeroes1, zeroes2}, + {zeroes1, ones2}, + {zeroes1, twos2}, + + {ones1, none2}, + {ones1, zeroes2}, + {ones1, ones2}, + {ones1, twos2}, + + {twos1, none2}, + {twos1, zeroes2}, + {twos1, ones2}, + {twos1, twos2}, + }; + + for (auto const& a : examples) + { + for (auto const& b : examples) + { + if (a < b) TEST_CHECK(a != b); + if (b < a) TEST_CHECK(a != b); + if (a != b) TEST_CHECK(b != a); + TEST_CHECK((a == b) == (b == a)); + TEST_CHECK((a == b) == !(b != a)); + TEST_CHECK((a == b) == !(a != b)); + if (a < b) TEST_CHECK(!(b < a)); + TEST_CHECK((!(a < b) && !(b < a)) == (a == b)); + for (auto const& c : examples) + { + if (a < b && b < c) TEST_CHECK(a < c); + } + } + } +} + +TORRENT_TEST(has) +{ + { + lt::info_hash_t a{none1, none2}; + TEST_EQUAL(a.has_v1(), false); + TEST_EQUAL(a.has_v2(), false); + TEST_EQUAL(a.has(protocol_version::V1), false); + TEST_EQUAL(a.has(protocol_version::V2), false); + TEST_EQUAL(a.get_best(), none1); + } + + { + lt::info_hash_t a{ones1, none2}; + TEST_EQUAL(a.has_v1(), true); + TEST_EQUAL(a.has_v2(), false); + TEST_EQUAL(a.has(protocol_version::V1), true); + TEST_EQUAL(a.has(protocol_version::V2), false); + TEST_EQUAL(a.get_best(), ones1); + } + + { + lt::info_hash_t a{ones1, twos2}; + TEST_EQUAL(a.has_v1(), true); + TEST_EQUAL(a.has_v2(), true); + TEST_EQUAL(a.has(protocol_version::V1), true); + TEST_EQUAL(a.has(protocol_version::V2), true); + TEST_EQUAL(a.get_best(), twos1); + } + + { + lt::info_hash_t a{none1, ones2}; + TEST_EQUAL(a.has_v1(), false); + TEST_EQUAL(a.has_v2(), true); + TEST_EQUAL(a.has(protocol_version::V1), false); + TEST_EQUAL(a.has(protocol_version::V2), true); + TEST_EQUAL(a.get_best(), ones1); + } +} + +TORRENT_TEST(std_hash) +{ + std::unordered_set test; + + test.emplace(none1, none2); + test.emplace(none1, zeroes2); + test.emplace(none1, ones2); + test.emplace(none1, twos2); + + test.emplace(zeroes1, none2); + test.emplace(zeroes1, zeroes2); + test.emplace(zeroes1, ones2); + test.emplace(zeroes1, twos2); + + test.emplace(ones1, none2); + test.emplace(ones1, zeroes2); + test.emplace(ones1, ones2); + test.emplace(ones1, twos2); + + test.emplace(twos1, none2); + test.emplace(twos1, zeroes2); + test.emplace(twos1, ones2); + test.emplace(twos1, twos2); + + TEST_EQUAL(test.size(), 16); +} diff --git a/test/test_io.cpp b/test/test_io.cpp new file mode 100644 index 0000000..614dc83 --- /dev/null +++ b/test/test_io.cpp @@ -0,0 +1,291 @@ +/* + +Copyright (c) 2018-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 "test.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/aux_/io.hpp" +#include "libtorrent/span.hpp" + +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..912676c --- /dev/null +++ b/test/test_ip_filter.cpp @@ -0,0 +1,260 @@ +/* + +Copyright (c) 2005-2009, 2013, 2015-2017, 2019, Arvid Norberg +Copyright (c) 2016-2018, 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/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" +#include "libtorrent/session_params.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 +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(aux::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); +} + +std::vector> const 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} +}; + +// **** test joining of ranges at the end **** +TORRENT_TEST(joining_ranges_at_end) +{ + 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); + + auto const range = std::get<0>(f.export_filter()); + test_rules_invariant(range, f); + + TEST_CHECK(range == expected1); +} + +// **** test joining of ranges at the start **** +TORRENT_TEST(joining_ranges_at_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); + + auto const range = std::get<0>(f.export_filter()); + test_rules_invariant(range, f); + + TEST_CHECK(range == expected1); +} + +// **** test joining of overlapping ranges at the start **** +TORRENT_TEST(joining_overlapping_ranges_at_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); + + auto const range = std::get<0>(f.export_filter()); + test_rules_invariant(range, f); + + TEST_CHECK(range == expected1); +} + +// **** test joining of overlapping ranges at the end **** +TORRENT_TEST(joining_overlapping_ranges_at_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); + + auto const range = std::get<0>(f.export_filter()); + test_rules_invariant(range, f); + + TEST_CHECK(range == expected1); +} + +// **** test joining of multiple overlapping ranges 1 **** +TORRENT_TEST(joining_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); + + auto const range = std::get<0>(f.export_filter()); + test_rules_invariant(range, f); + + std::vector> const 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(range == expected); +} + +// **** test joining of multiple overlapping ranges 2 **** +TORRENT_TEST(joining_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); + + auto const range = std::get<0>(f.export_filter()); + test_rules_invariant(range, f); + + std::vector> const 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(range == expected); +} + +// **** test IPv6 **** +TORRENT_TEST(ipv6) +{ + std::vector> const 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_CHECK(rangev6 == expected2); +} + +TORRENT_TEST(default_empty) +{ + { + ip_filter f; + TEST_CHECK(f.empty()); + + f.add_rule(addr("1::"), addr("2::"), ip_filter::blocked); + TEST_CHECK(!f.empty()); + } + + { + ip_filter f; + f.add_rule(addr("0.0.1.0"), addr("7.0.4.0"), ip_filter::blocked); + TEST_CHECK(!f.empty()); + } + + { + ip_filter f; + f.add_rule(addr("0.0.1.0"), addr("7.0.4.0"), 0); + TEST_CHECK(f.empty()); + } +} + +TORRENT_TEST(port_filter) +{ + 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..25c6bc7 --- /dev/null +++ b/test/test_ip_voter.cpp @@ -0,0 +1,223 @@ +/* + +Copyright (c) 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Steven Siloti +Copyright (c) 2018, 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/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 "setup_transfer.hpp" // for rand_v4, supports_ipv6 + +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(make_address_v4("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(make_address_v4("51.1.1.1")); + address_v4 addr2(make_address_v4("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(make_address_v4("93.12.63.174")); + address_v4 addr1(make_address_v4("51.1.1.1")); + address_v4 addr2(make_address_v4("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 = make_address_v4("5.5.5.5", ec); + TEST_CHECK(!ec); + address malicious = make_address_v4("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 = make_address_v4("4.4.4.4", ec); + TEST_CHECK(!ec); + address real_external1 = make_address_v4("5.5.5.5", ec); + TEST_CHECK(!ec); + address malicious_external = make_address_v4("3.3.3.3", ec); + TEST_CHECK(!ec); + + address malicious2; + address real_external2; + address malicious_external2; + if (supports_ipv6()) + { + malicious2 = make_address_v6("2f90::", ec); + TEST_CHECK(!ec); + real_external2 = make_address_v6("2f80::", ec); + TEST_CHECK(!ec); + malicious_external2 = make_address_v6("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_listen_socket.cpp b/test/test_listen_socket.cpp new file mode 100644 index 0000000..44acd2d --- /dev/null +++ b/test/test_listen_socket.cpp @@ -0,0 +1,538 @@ +/* + +Copyright (c) 2016-2017, Steven Siloti +Copyright (c) 2017, 2019-2020, Arvid Norberg +Copyright (c) 2018, 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/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 = make_address(ip); + if (netmask) ipi.netmask = make_address(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 = make_address(ip); + if (netmask) ipi.netmask = make_address(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 = make_address(ip); + ret.gateway = make_address(gateway); + std::strncpy(ret.name, device, sizeof(ret.name) - 1); + ret.name[sizeof(ret.name) - 1] = '\0'; + ret.mtu = 1500; + 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(make_address(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(make_address(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(make_address(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(make_address(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(make_address(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(make_address(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(), make_address_v4("4.4.4.4"), 6881, "", tp::plaintext); + test_equal(*sockets.back(), address_v4(), 6881, "", tp::plaintext); + test_equal(eps.front(), make_address_v4("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(), make_address_v4("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(), make_address_v4("10.10.10.10"), 6881, "enp3s0", tp::plaintext); + test_equal(*sockets.back(), make_address_v4("4.4.4.4"), 6881, "enp3s0", tp::plaintext); + TEST_EQUAL(eps.size(), 1); + test_equal(eps.front(), make_address_v4("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 = { + { + make_address("127.0.0.1"), + 6881, // port + "", // device + aux::transport::plaintext, + aux::listen_socket_flags_t{} }, + { + make_address("192.168.1.2"), + 6881, // port + "", // device + aux::transport::plaintext, + aux::listen_socket_flags_t{} } + }; + + expand_devices(ifs, eps); + + TEST_CHECK((eps == std::vector{ + { + make_address("127.0.0.1"), + 6881, // port + "lo", // device + aux::transport::plaintext, + aux::listen_socket_flags_t{}, + make_address("255.0.0.0") }, + { + make_address("192.168.1.2"), + 6881, // port + "eth0", // device + aux::transport::plaintext, + aux::listen_socket_flags_t{}, + make_address("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 = { + rt("0.0.0.0", "eth99", "0.0.0.0"), + }; + + 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 l= false) +{ + return {std::string(dev), port, ssl, l}; +} +} +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..6eca949 --- /dev/null +++ b/test/test_lsd.cpp @@ -0,0 +1,131 @@ +/* + +Copyright (c) 2007-2010, 2013-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016, Luca Bruno +Copyright (c) 2018, d-komarov +Copyright (c) 2018, 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/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/aux_/path.hpp" +#include + +#include "test.hpp" +#include "test_utils.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, test_listen_interface()); +#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, test_listen_interface()); + 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..7e0692b --- /dev/null +++ b/test/test_magnet.cpp @@ -0,0 +1,672 @@ +/* + +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2016, 2018, Steven Siloti +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Jan Berkel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/session_params.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"); +} +#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::close_redundant_connections, false); + pack.set_int(settings_pack::auto_scrape_interval, 235); + pack.set_int(settings_pack::auto_scrape_min_interval, 62); + pack.set_int(settings_pack::dht_max_peers_reply, 70); + auto s = std::make_unique(pack); + + TEST_EQUAL(pack.get_str(settings_pack::user_agent), "test"); + TEST_EQUAL(pack.get_int(settings_pack::tracker_receive_timeout), 1234); + + entry session_state = write_session_params(s->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_hashes().v1; + TEST_EQUAL(aux::to_hex(ih), "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); + + p1 = s->abort(); + + 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.reset(new lt::session(read_session_params(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_hashes.v1), "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_hashes.v1), "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); +} + +TORRENT_TEST(parse_mixed_case) +{ + add_torrent_params p = parse_magnet_uri("magnet:?XT=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc%64&dN=foobar&Ws=http://foo.com/bar"); + TEST_EQUAL(aux::to_hex(p.info_hashes.v1), "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); + TEST_EQUAL(p.name, "foobar"); + TEST_EQUAL(p.url_seeds.size(), 1); + TEST_EQUAL(p.url_seeds[0], "http://foo.com/bar"); +} + +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(parse_magnet_uri_quoted) +{ + add_torrent_params p = parse_magnet_uri("magnet:?\"foo=bar&xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); + TEST_EQUAL(aux::to_hex(p.info_hashes.v1), "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); +} + +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_hashes.v1, 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_v2_hash) +{ + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btmh:1220cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); + TEST_EQUAL(aux::to_hex(p.info_hashes.v2), "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); + TEST_EQUAL(aux::to_hex(p.info_hashes.v1), "0000000000000000000000000000000000000000"); +} + +TORRENT_TEST(parse_v2_short_hash) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btmh:1220cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdccdcdcdcdcdcdcd", ec); + TEST_EQUAL(ec, error_code(errors::invalid_info_hash)); +} + +TORRENT_TEST(parse_v2_invalid_hash_prefix) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btmh:1221cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", ec); + TEST_EQUAL(ec, error_code(errors::invalid_info_hash)); +} + +TORRENT_TEST(parse_hybrid_uri) +{ + add_torrent_params p = parse_magnet_uri("magnet:?" + "xt=urn:btmh:1220cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); + TEST_EQUAL(aux::to_hex(p.info_hashes.v1), "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); + TEST_EQUAL(aux::to_hex(p.info_hashes.v2), "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); +} + +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(make_magnet_uri_v2) +{ + auto ti = ::create_torrent(nullptr, "temporary", 16 * 1024, 13 + , true, lt::create_torrent::v2_only); + + std::string magnet = make_magnet_uri(*ti); + std::printf("%s len: %d\n", magnet.c_str(), int(magnet.size())); + TEST_CHECK(magnet.find("xt=urn:btmh:1220") != std::string::npos); + TEST_CHECK(magnet.find("xt=urn:btih:") == std::string::npos); +} + +TORRENT_TEST(make_magnet_uri_hybrid) +{ + auto ti = ::create_torrent(nullptr, "temporary", 16 * 1024, 13); + + std::string magnet = make_magnet_uri(*ti); + std::printf("%s len: %d\n", magnet.c_str(), int(magnet.size())); + TEST_CHECK(magnet.find("xt=urn:btih:") != std::string::npos); + TEST_CHECK(magnet.find("xt=urn:btmh:1220") != std::string::npos); +} + +TORRENT_TEST(make_magnet_uri_v1) +{ + auto ti = ::create_torrent(nullptr, "temporary", 16 * 1024, 13, true, lt::create_torrent::v1_only); + + std::string magnet = make_magnet_uri(*ti); + std::printf("%s len: %d\n", magnet.c_str(), int(magnet.size())); + TEST_CHECK(magnet.find("xt=urn:btih:") != std::string::npos); + TEST_CHECK(magnet.find("xt=urn:btmh:1220") == 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..3b58fff --- /dev/null +++ b/test/test_merkle.cpp @@ -0,0 +1,1317 @@ +/* + +Copyright (c) 2015, 2019-2020, Arvid Norberg +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 "test.hpp" +#include "libtorrent/aux_/merkle.hpp" +#include "libtorrent/bitfield.hpp" +#include + +using namespace lt; + +namespace { + + void compare_bits(bitfield const& bits, char const* str) + { + for (int i = 0; *str; ++i, ++str) + { + if (*str == '1') + TEST_CHECK(bits.get_bit(i)); + else if (*str == '0') + TEST_CHECK(!bits.get_bit(i)); + else + TEST_CHECK(false); + } + } +} + +TORRENT_TEST(num_leafs) +{ + // 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); +} + +TORRENT_TEST(get_parent) +{ + // 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); +} + +TORRENT_TEST(get_sibling) +{ + // 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); +} + +TORRENT_TEST(num_nodes) +{ + // total number of nodes given the number of leaves + 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); +} + +TORRENT_TEST(first_leaf) +{ + // this is the structure: + // 0 + // 1 2 + // 3 4 5 6 + // 7 8 9 10 11 12 13 14 + // total number of nodes given the number of leaves + TEST_EQUAL(merkle_first_leaf(1), 0); + TEST_EQUAL(merkle_first_leaf(2), 1); + TEST_EQUAL(merkle_first_leaf(4), 3); + TEST_EQUAL(merkle_first_leaf(8), 7); + TEST_EQUAL(merkle_first_leaf(16), 15); +} + +TORRENT_TEST(get_layer) +{ + // this is the structure: + // 0 + // 1 2 + // 3 4 5 6 + // 7 8 9 10 11 12 13 14 + + TEST_EQUAL(merkle_get_layer(0), 0); + TEST_EQUAL(merkle_get_layer(1), 1); + TEST_EQUAL(merkle_get_layer(2), 1); + TEST_EQUAL(merkle_get_layer(3), 2); + TEST_EQUAL(merkle_get_layer(4), 2); + TEST_EQUAL(merkle_get_layer(5), 2); + TEST_EQUAL(merkle_get_layer(6), 2); + TEST_EQUAL(merkle_get_layer(7), 3); + TEST_EQUAL(merkle_get_layer(8), 3); + TEST_EQUAL(merkle_get_layer(9), 3); + TEST_EQUAL(merkle_get_layer(10), 3); + TEST_EQUAL(merkle_get_layer(11), 3); + TEST_EQUAL(merkle_get_layer(12), 3); + TEST_EQUAL(merkle_get_layer(13), 3); + TEST_EQUAL(merkle_get_layer(14), 3); + TEST_EQUAL(merkle_get_layer(15), 4); +} + +TORRENT_TEST(get_layer_offset) +{ + // given a node index, how many steps from the left of the tree is that node? + TEST_EQUAL(merkle_get_layer_offset(0), 0); + TEST_EQUAL(merkle_get_layer_offset(1), 0); + TEST_EQUAL(merkle_get_layer_offset(2), 1); + TEST_EQUAL(merkle_get_layer_offset(3), 0); + TEST_EQUAL(merkle_get_layer_offset(4), 1); + TEST_EQUAL(merkle_get_layer_offset(5), 2); + TEST_EQUAL(merkle_get_layer_offset(6), 3); + TEST_EQUAL(merkle_get_layer_offset(7), 0); + TEST_EQUAL(merkle_get_layer_offset(8), 1); + TEST_EQUAL(merkle_get_layer_offset(9), 2); + TEST_EQUAL(merkle_get_layer_offset(10), 3); + TEST_EQUAL(merkle_get_layer_offset(11), 4); + TEST_EQUAL(merkle_get_layer_offset(12), 5); + TEST_EQUAL(merkle_get_layer_offset(13), 6); + TEST_EQUAL(merkle_get_layer_offset(14), 7); + TEST_EQUAL(merkle_get_layer_offset(15), 0); +} + +TORRENT_TEST(merkle_num_layers) +{ + TEST_EQUAL(merkle_num_layers(0), 0); + TEST_EQUAL(merkle_num_layers(1), 0); + TEST_EQUAL(merkle_num_layers(2), 1); + TEST_EQUAL(merkle_num_layers(4), 2); + TEST_EQUAL(merkle_num_layers(8), 3); + TEST_EQUAL(merkle_num_layers(16), 4); +} + +TORRENT_TEST(merkle_get_first_child) +{ + // this is the structure: + // 0 + // 1 2 + // 3 4 5 6 + // 7 8 9 10 11 12 13 14 + TEST_EQUAL(merkle_get_first_child(0), 1); + TEST_EQUAL(merkle_get_first_child(1), 3); + TEST_EQUAL(merkle_get_first_child(2), 5); + TEST_EQUAL(merkle_get_first_child(3), 7); + TEST_EQUAL(merkle_get_first_child(4), 9); + TEST_EQUAL(merkle_get_first_child(5), 11); + TEST_EQUAL(merkle_get_first_child(6), 13); + TEST_EQUAL(merkle_get_first_child(7), 15); + TEST_EQUAL(merkle_get_first_child(8), 17); + TEST_EQUAL(merkle_get_first_child(9), 19); + TEST_EQUAL(merkle_get_first_child(10), 21); + TEST_EQUAL(merkle_get_first_child(11), 23); + TEST_EQUAL(merkle_get_first_child(12), 25); + TEST_EQUAL(merkle_get_first_child(13), 27); + TEST_EQUAL(merkle_get_first_child(14), 29); + TEST_EQUAL(merkle_get_first_child(15), 31); + TEST_EQUAL(merkle_get_first_child(16), 33); +} + +TORRENT_TEST(merkle_get_first_child2) +{ + // this is the structure: + // 0 + // 1 2 + // 3 4 5 6 + // 7 8 9 10 11 12 13 14 + // 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 + // 31 ... + + TEST_EQUAL(merkle_get_first_child(0, 0), 0); + TEST_EQUAL(merkle_get_first_child(0, 1), 1); + TEST_EQUAL(merkle_get_first_child(0, 2), 3); + TEST_EQUAL(merkle_get_first_child(0, 3), 7); + TEST_EQUAL(merkle_get_first_child(0, 4), 15); + TEST_EQUAL(merkle_get_first_child(0, 5), 31); + + TEST_EQUAL(merkle_get_first_child(2, 0), 2); + TEST_EQUAL(merkle_get_first_child(2, 1), 5); + TEST_EQUAL(merkle_get_first_child(2, 2), 11); + TEST_EQUAL(merkle_get_first_child(2, 3), 23); + TEST_EQUAL(merkle_get_first_child(2, 4), 47); + TEST_EQUAL(merkle_get_first_child(2, 5), 95); +} + +TORRENT_TEST(merkle_layer_start) +{ + TEST_EQUAL(merkle_layer_start(0), 0); + TEST_EQUAL(merkle_layer_start(1), 1); + TEST_EQUAL(merkle_layer_start(2), 3); + TEST_EQUAL(merkle_layer_start(3), 7); + TEST_EQUAL(merkle_layer_start(4), 15); + TEST_EQUAL(merkle_layer_start(5), 31); + TEST_EQUAL(merkle_layer_start(6), 63); + TEST_EQUAL(merkle_layer_start(7), 127); + TEST_EQUAL(merkle_layer_start(8), 255); + TEST_EQUAL(merkle_layer_start(9), 511); +} + +TORRENT_TEST(merkle_to_flat_index) +{ + TEST_EQUAL(merkle_to_flat_index(0, 0), 0); + TEST_EQUAL(merkle_to_flat_index(1, 0), 1); + TEST_EQUAL(merkle_to_flat_index(1, 1), 2); + TEST_EQUAL(merkle_to_flat_index(2, 0), 3); + TEST_EQUAL(merkle_to_flat_index(2, 1), 4); + TEST_EQUAL(merkle_to_flat_index(2, 2), 5); + TEST_EQUAL(merkle_to_flat_index(2, 3), 6); + TEST_EQUAL(merkle_to_flat_index(3, 0), 7); + TEST_EQUAL(merkle_to_flat_index(3, 1), 8); + TEST_EQUAL(merkle_to_flat_index(3, 2), 9); + TEST_EQUAL(merkle_to_flat_index(3, 3), 10); + TEST_EQUAL(merkle_to_flat_index(3, 4), 11); + TEST_EQUAL(merkle_to_flat_index(3, 5), 12); + TEST_EQUAL(merkle_to_flat_index(3, 6), 13); + TEST_EQUAL(merkle_to_flat_index(3, 7), 14); +} + +namespace { +sha256_hash H(sha256_hash left, sha256_hash right) +{ + hasher256 st; + st.update(left); + st.update(right); + return st.final(); +} +} + +using v = std::vector; +sha256_hash const a("11111111111111111111111111111111"); +sha256_hash const b("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); +sha256_hash const c("cccccccccccccccccccccccccccccccc"); +sha256_hash const d("dddddddddddddddddddddddddddddddd"); +sha256_hash const e("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"); +sha256_hash const f("ffffffffffffffffffffffffffffffff"); +sha256_hash const g("gggggggggggggggggggggggggggggggg"); +sha256_hash const h("iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"); + +// 0 and 1 +sha256_hash const o; +sha256_hash const l("11111111111111111111111111111111"); + +// combinations +sha256_hash const ab = H(a,b); +sha256_hash const cd = H(c,d); +sha256_hash const ef = H(e,f); +sha256_hash const gh = H(g,h); + +sha256_hash const ad = H(ab,cd); +sha256_hash const eh = H(ef,gh); + +sha256_hash const ah = H(ad,eh); + +TORRENT_TEST(merkle_fill_tree) +{ + // fill whole tree + { + v tree{ + o, + o, o, + o, o, o, o, + a,b,c,d,e,f,g,h}; + + merkle_fill_tree(tree, 8, 7); + + TEST_CHECK((tree == + v{ + ah, + ad, eh, + ab, cd, ef, gh, + a,b,c,d,e,f,g,h})); + } + + // fill left side of the tree + { + v tree{ + o, + o, o, + ab,cd, o, o, + a,b,c,d,o,o,o,o}; + + merkle_fill_tree(tree, 4, 7); + + TEST_CHECK((tree == + v{o, + ad, o, + ab, cd, o, o, + a,b,c,d,o,o,o,o})); + } + + // fill right side of the tree + { + v tree{ + o, + o, o, + o, o, o, o, + o,o,o,o,a,b,c,d}; + + merkle_fill_tree(tree, 4, 11); + + TEST_CHECK((tree == + v{o, + o, ad, + o, o, ab,cd, + o,o,o,o,a,b,c,d})); + } + + // fill shallow left of the tree + { + v tree{ + o, + o, o, + a, b, o, o, + o,o,o,o,o,o,o,o}; + + merkle_fill_tree(tree, 2, 3); + + TEST_CHECK((tree == + v{o, + ab, o, + a, b, o, o, + o,o,o,o,o,o,o,o})); + } + + // fill shallow right of the tree + { + v tree{ + o, + o, o, + o, o, a, b, + o,o,o,o,o,o,o,o}; + + merkle_fill_tree(tree, 2, 5); + + TEST_CHECK((tree == + v{o, + o, ab, + o, o, a, b, + o,o,o,o,o,o,o,o})); + } +} + +TORRENT_TEST(merkle_fill_partial_tree) +{ + // fill whole tree + { + v tree{o, + o, o, + o, o, o, o, + a,b,c,d,e,f,g,h}; + + merkle_fill_partial_tree(tree); + + TEST_CHECK((tree == + v{ah, + ad, eh, + ab, cd, ef, gh, + a,b,c,d,e,f,g,h})); + } + + // fill left side of the tree + { + v tree{o, + o, eh, + ab,cd, o, o, + a,b,c,d,o,o,o,o}; + + merkle_fill_partial_tree(tree); + + TEST_CHECK((tree == + v{ah, + ad, eh, + ab, cd, o, o, + a,b,c,d,o,o,o,o})); + } + + // fill right side of the tree + { + v tree{o, + ad, o, + o, o, o, o, + o,o,o,o,e,f,g,h}; + + merkle_fill_partial_tree(tree); + + TEST_CHECK((tree == + v{ah, + ad, eh, + o, o, ef,gh, + o,o,o,o,e,f,g,h})); + } + + // fill shallow left of the tree + { + v tree{ + o, + o, eh, + ab, cd, o, o, + o,o,o,o,o,o,o,o}; + + merkle_fill_partial_tree(tree); + + TEST_CHECK((tree == + v{ah, + ad, eh, + ab, cd, o, o, + o,o,o,o,o,o,o,o})); + } + + // fill shallow right of the tree + { + v tree{ + o, + ad, o, + o, o, ef,gh, + o,o,o,o,o,o,o,o}; + + merkle_fill_partial_tree(tree); + + TEST_CHECK((tree == + v{ah, + ad, eh, + o, o, ef, gh, + o,o,o,o,o,o,o,o})); + } + + // fill uneven tree + { + v tree{ + o, + ad, o, + o, o, ef, gh, + o,o,o,o,o,o,o,o}; + + merkle_fill_partial_tree(tree); + + TEST_CHECK((tree == + v{ah, + ad, eh, + o, o, ef, gh, + o,o,o,o,o,o,o,o})); + } + + // clear orphans + { + v tree{ + o, + ad, ah, + o, o, ef, gh, + a,o,c,o,o,o,o,o}; + + merkle_fill_partial_tree(tree); + + TEST_CHECK((tree == + v{ah, + ad, eh, + o, o, ef,gh, + o,o,o,o,o,o,o,o})); + } + + // clear orphan sub-tree + { + v tree{o, + o, o, + o, o, o, o, + a,b,c,d,o,o,o,o}; + + merkle_fill_partial_tree(tree); + + TEST_CHECK((tree == + v{o, + o, o, + o, o, o, o, + o,o,o,o,o,o,o,o})); + } + + // fill sub-tree + { + v tree{o, + o, eh, + o, o, o, o, + a,b,c,d,o,o,o,o}; + + merkle_fill_partial_tree(tree); + + TEST_CHECK((tree == + v{ah, + ad, eh, + ab,cd, o, o, + a,b,c,d,o,o,o,o})); + } + + // clear no-siblings left + { + v tree{ + o, + ad, ah, + o, o, ef, gh, + o,o,o,o,o,o,o,h}; + + merkle_fill_partial_tree(tree); + + TEST_CHECK((tree == + v{ah, + ad, eh, + o, o, ef, gh, + o,o,o,o,o,o,o,o})); + } + + // clear no-siblings right + { + v tree{ + o, + ad, ah, + o, o, ef, gh, + o,o,o,o,o,o,g,o}; + + merkle_fill_partial_tree(tree); + + TEST_CHECK((tree == + v{ah, + ad, eh, + o, o, ef, gh, + o,o,o,o,o,o,o,o})); + } + + // fill gaps + { + v tree{ + o, + ad, ah, + o, o, ef,gh, + a,b,c,d,o,o,o,o}; + + merkle_fill_partial_tree(tree); + + TEST_CHECK((tree == + v{ah, + ad, eh, + ab,cd, ef,gh, + a,b,c,d,o,o,o,o})); + } +} + +TORRENT_TEST(merkle_root) +{ + // all leaves in the tree + TEST_CHECK(merkle_root(v{a,b,c,d,e,f,g,h}, o) == ah); + + // not power-of-two number of leaves + TEST_CHECK(merkle_root(v{a,b,c,d,e,f}, o) == H(ad, H(ef, H(o, o)))); + + // very small tree + TEST_CHECK(merkle_root(v{a,b}, o) == ab); + + // single hash-tree + TEST_CHECK(merkle_root(v{a}) == a); +} + +TORRENT_TEST(merkle_root_scratch) +{ + std::vector buf; + + // all leaves in the tree + TEST_CHECK(merkle_root_scratch(v{a,b,c,d,e,f,g,h}, 8, o, buf) == ah); + + // not power-of-two number of leaves + TEST_CHECK(merkle_root_scratch(v{a,b,c,d,e,f}, 8, o, buf) == H(ad, H(ef, H(o, o)))); + + // very small tree + TEST_CHECK(merkle_root_scratch(v{a,b}, 2, o, buf) == ab); + + // unaligned leaf layer + TEST_CHECK(merkle_root_scratch(v{a,b,c}, 8, o, buf) == H(H(ab, H(c, o)), H(H(o,o), H(o,o)))); +} + +namespace { +void print_tree(span tree) +{ + int const num_leafs = static_cast((tree.size() + 1) / 2); + int spacing = num_leafs; + int const num_levels = merkle_num_layers(num_leafs) + 1; + int layer_width = 1; + int node = 0; + for (int i = 0; i < num_levels; ++i) + { + for (int k = 0; k < layer_width; ++k) + { + for (int j = 0; j < spacing; ++j) std::cout << ' '; + std::cout << (tree[node] == sha256_hash() ? '0' : '1'); + for (int j = 0; j < spacing - 1; ++j) std::cout << ' '; + ++node; + } + std::cout << '\n'; + layer_width *= 2; + spacing /= 2; + } + std::cout << '\n'; +} +} + +TORRENT_TEST(merkle_clear_tree) +{ + // test clearing the whole tree + { + v tree{l, + l, l, + l, l, l, l, + l,l,l,l,l,l,l,l}; + + print_tree(tree); + merkle_clear_tree(tree, 8, 7); + print_tree(tree); + + TEST_CHECK((tree == + v{o, + o, o, + o, o, o, o, + o,o,o,o,o,o,o,o})); + } + + // test clearing the left side of the tree + { + v tree{l, + l, l, + l, l, l, l, + l,l,l,l,l,l,l,l}; + + print_tree(tree); + merkle_clear_tree(tree, 4, 7); + print_tree(tree); + + TEST_CHECK((tree == + v{l, + o, l, + o, o, l, l, + o,o,o,o,l,l,l,l})); + } + + // test clearing the right side of the tree + { + v tree{l, + l, l, + l, l, l, l, + l,l,l,l,l,l,l,l}; + + print_tree(tree); + merkle_clear_tree(tree, 4, 11); + print_tree(tree); + + TEST_CHECK((tree == + v{l, + l, o, + l, l, o, o, + l,l,l,l,o,o,o,o})); + } + + // test clearing shallow left + { + v tree{l, + l, l, + l, l, l, l, + l,l,l,l,l,l,l,l}; + + print_tree(tree); + merkle_clear_tree(tree, 2, 3); + print_tree(tree); + + TEST_CHECK((tree == + v{l, + o, l, + o, o, l, l, + l,l,l,l,l,l,l,l})); + } + + // test clearing shallow right + { + v tree{l, + l, l, + l, l, l, l, + l,l,l,l,l,l,l,l}; + + print_tree(tree); + merkle_clear_tree(tree, 2, 5); + print_tree(tree); + + TEST_CHECK((tree == + v{l, + l, o, + l, l, o, o, + l,l,l,l,l,l,l,l})); + } +} + +TORRENT_TEST(merkle_pad) +{ + // if the block layer is the same as the piece layer, the pad is always just + // zeroes + TEST_CHECK(merkle_pad(1, 1) == sha256_hash{}); + TEST_CHECK(merkle_pad(2, 2) == sha256_hash{}); + TEST_CHECK(merkle_pad(4, 4) == sha256_hash{}); + TEST_CHECK(merkle_pad(8, 8) == sha256_hash{}); + TEST_CHECK(merkle_pad(16, 16) == sha256_hash{}); + + // if the block layer is one step below the piece layer, the pad is always + // SHA256(0 .. 0). i.e. two zero hashes hashed. + + auto const pad1 = [] { + hasher256 ctx; + ctx.update(sha256_hash{}); + ctx.update(sha256_hash{}); + return ctx.final(); + }(); + TEST_CHECK(merkle_pad(2, 1) == pad1); + TEST_CHECK(merkle_pad(4, 2) == pad1); + TEST_CHECK(merkle_pad(8, 4) == pad1); + TEST_CHECK(merkle_pad(16, 8) == pad1); + + auto const pad2 = [&] { + hasher256 ctx; + ctx.update(pad1); + ctx.update(pad1); + return ctx.final(); + }(); + TEST_CHECK(merkle_pad(4, 1) == pad2); + TEST_CHECK(merkle_pad(8, 2) == pad2); + TEST_CHECK(merkle_pad(16, 4) == pad2); + TEST_CHECK(merkle_pad(32, 8) == pad2); +} + +TORRENT_TEST(merkle_validate_node) +{ + TEST_CHECK(merkle_validate_node(a, b, ab)); + TEST_CHECK(merkle_validate_node(c, d, cd)); + TEST_CHECK(merkle_validate_node(e, f, ef)); + TEST_CHECK(merkle_validate_node(g, h, gh)); + + TEST_CHECK(merkle_validate_node(ab, cd, ad)); + TEST_CHECK(merkle_validate_node(ef, gh, eh)); + + TEST_CHECK(merkle_validate_node(ad, eh, ah)); + + TEST_CHECK(!merkle_validate_node(b, a, ab)); + TEST_CHECK(!merkle_validate_node(d, c, cd)); + TEST_CHECK(!merkle_validate_node(f, e, ef)); + TEST_CHECK(!merkle_validate_node(h, g, gh)); +} + +TORRENT_TEST(merkle_validate_copy_full) +{ + v const src{ + ah, + ad, eh, + ab, cd, ef, gh, + a,b,c,d,e,f,g,h}; + + v empty_tree(15); + bitfield verified(8); + + merkle_validate_copy(src, empty_tree, ah, verified); + + compare_bits(verified, "11111111"); + TEST_CHECK(empty_tree == src); +} + +TORRENT_TEST(merkle_validate_copy_full_odd_nodes) +{ + v const src{ + ah, + ad, eh, + ab, cd, ef, gh, + a,b,c,d,e,f,g,h}; + + v empty_tree(15); + // we pretend that h is a padding node. This algorithm doesn't care that + // it's not zero (yet) + bitfield verified(7); + + merkle_validate_copy(src, empty_tree, ah, verified); + + compare_bits(verified, "1111111"); + TEST_CHECK(empty_tree == src); +} + + +TORRENT_TEST(merkle_validate_copy_invalid_leaf) +{ + v const src{ + ah, + ad, eh, + ab, cd, ef, gh, + a,b,c,d,e,ef,g,h}; + + v empty_tree(15); + bitfield verified(8); + + merkle_validate_copy(src, empty_tree, ah, verified); + + // leaf 5 had an invalid hash, it's sibling (leaf 4) could also not be + // validated because of it + compare_bits(verified, "11110011"); + + v const expected{ + ah, + ad, eh, + ab, cd, ef, gh, + a,b,c,d,o,o,g,h}; + TEST_CHECK(empty_tree == expected); +} + +TORRENT_TEST(merkle_validate_copy_many_invalid_leafs) +{ + v const src{ + ah, + ad, eh, + ab, cd, ef, gh, + a,b,ef,d,eh,ef,g,ah}; + + v empty_tree(15); + bitfield verified(8); + + merkle_validate_copy(src, empty_tree, ah, verified); + + // leaf 2,4, 5 and 7 had an invalid hash, their siblings (leaf 3 and 6) could also not be + // validated because of it + compare_bits(verified, "11000000"); + + v const expected{ + ah, + ad, eh, + ab, cd, ef, gh, + a,b,o,o,o,o,o,o}; + TEST_CHECK(empty_tree == expected); +} + +TORRENT_TEST(merkle_validate_copy_partial) +{ + v const src{ + ah, + ad, eh, + ab, cd, ef, o, + a,b,c,o,o,o,o,o}; + + v empty_tree(15); + bitfield verified(8); + + merkle_validate_copy(src, empty_tree, ah, verified); + + compare_bits(verified, "11000000"); + + v const expected{ + ah, + ad, eh, + ab, cd, o, o, + a,b,o,o,o,o,o,o}; + + TEST_CHECK(empty_tree == expected); +} + +TORRENT_TEST(merkle_validate_copy_invalid_root) +{ + v const src{ + ah, + ad, eh, + ab, cd, ef, o, + a,b,c,o,o,o,o,o}; + + v empty_tree(15); + bitfield verified(8); + + merkle_validate_copy(src, empty_tree, a, verified); + + v const expected(15); + + compare_bits(verified, "00000000"); + TEST_CHECK(empty_tree == expected); +} + +TORRENT_TEST(merkle_validate_copy_root_only) +{ + v const src{ + ah, + o, o, + o, o, o, o, + o,o,o,o,o,o,o,o}; + + v empty_tree(15); + bitfield verified(8); + + merkle_validate_copy(src, empty_tree, ah, verified); + + compare_bits(verified, "00000000"); + + v const expected{ + ah, + o, o, + o, o, o, o, + o,o,o,o,o,o,o,o}; + + TEST_CHECK(empty_tree == expected); +} + +TORRENT_TEST(merkle_validate_single_leayer_fail_no_parents) +{ + v const src{ + o, + o, o, + o, o, o, o, + a,b,c,d,e,f,g,h}; + + TEST_CHECK(!merkle_validate_single_layer(src)); +} + +TORRENT_TEST(merkle_validate_single_layer_missing_parent) +{ + v const src{ + o, + o, o, + ab, cd, o,gh, + a,b,c,d,e,f,g,h}; + + TEST_CHECK(!merkle_validate_single_layer(src)); +} + +TORRENT_TEST(merkle_validate_single_layer_missing_leaf) +{ + v const src{ + o, + o, o, + ab, cd, ef,gh, + a,b,c,o,e,f,g,h}; + + TEST_CHECK(!merkle_validate_single_layer(src)); +} + +TORRENT_TEST(merkle_validate_single_layer) +{ + v const src{ + o, + o, o, + ab, cd, ef,gh, + a,b,c,d,e,f,g,h}; + + TEST_CHECK(merkle_validate_single_layer(src)); +} + +TORRENT_TEST(is_subtree_known_full) +{ + v const src{ + ah, + ad, eh, + ab, cd, ef,gh, + a,b,c,d,e,f,g,h}; + + TEST_CHECK(merkle_find_known_subtree(src, 1, 8) == std::make_tuple(0, 2, 3)); +} + +TORRENT_TEST(is_subtree_known_two_levels) +{ + v const src{ + ah, + ad, eh, + o, o, ef,gh, + a,b,c,d,e,f,g,h}; + + TEST_CHECK(merkle_find_known_subtree(src, 1, 8) == std::make_tuple(0, 4, 1)); +} + +TORRENT_TEST(is_subtree_known_unknown) +{ + v const src{ + ah, + ad, eh, + o, o, ef,gh, + a,b,o,d,e,f,g,h}; + + TEST_CHECK(merkle_find_known_subtree(src, 1, 8) == std::make_tuple(0, 2, 3)); +} + +TORRENT_TEST(is_subtree_known_padding) +{ + // the last leaf is padding, it should be assumed to be correct despite + // being zero + v const src{ + ah, + ad, eh, + o, o, ef,gh, + a,b,o,d,e,f,g,o}; + + TEST_CHECK(merkle_find_known_subtree(src, 6, 7) == std::make_tuple(6, 2, 6)); +} + +TORRENT_TEST(is_subtree_known_padding_two_levels) +{ + // the last leaf is padding, it should be assumed to be correct despite + // being zero + v const src{ + ah, + ad, eh, + o, o, o, o, + a,b,o,d,e,f,g,o}; + + TEST_CHECK(merkle_find_known_subtree(src, 6, 7) == std::make_tuple(4, 4, 2)); +} + +TORRENT_TEST(is_subtree_known_more_padding_two_levels) +{ + // the last two leafs are padding, they should be assumed to be correct despite + // being zero + v const src{ + ah, + ad, eh, + o, o, o, o, + a,b,o,d,e,f,o,o}; + + TEST_CHECK(merkle_find_known_subtree(src, 5, 6) == std::make_tuple(4, 4, 2)); +} + +TORRENT_TEST(validate_and_insert_proofs_mixed) +{ +// full tree: +// ah +// ad eh +// ab cd ef gh +// a b c d e f g h + + v tree(15); + tree[0] = ah; + + v const proofs{f, gh, ad}; + + TEST_CHECK(merkle_validate_and_insert_proofs(tree, 11, e, proofs)); + TEST_CHECK((tree == v{ + ah, + ad, eh, + o, o, ef, gh, + o, o, o, o,e, f, o, o})); +} + +TORRENT_TEST(validate_and_insert_proofs_mixed_failure) +{ +// full tree: +// ah +// ad eh +// ab cd ef gh +// a b c d e f g h + + v tree(15); + tree[0] = eh; // this is not the correct root + + v const proofs{f, gh, ad}; + + TEST_CHECK(!merkle_validate_and_insert_proofs(tree, 11, e, proofs)); + + // make sure all nodes that were filled in were cleared correctly + TEST_CHECK((tree == v{ + eh, + o, o, + o, o, o, o, + o, o, o, o,o, o, o, o})); +} + +TORRENT_TEST(validate_and_insert_proofs_left) +{ +// full tree: +// ah +// ad eh +// ab cd ef gh +// a b c d e f g h + + v tree(15); + tree[0] = ah; + + v const proofs{b, cd, eh}; + + TEST_CHECK(merkle_validate_and_insert_proofs(tree, 7, a, proofs)); + TEST_CHECK((tree == v{ + ah, + ad, eh, + ab, cd, o, o, + a, b, o, o,o, o, o, o})); +} + +TORRENT_TEST(validate_and_insert_proofs_right) +{ +// full tree: +// ah +// ad eh +// ab cd ef gh +// a b c d e f g h + + v tree(15); + tree[0] = ah; + + v const proofs{g, ef, ad}; + + TEST_CHECK(merkle_validate_and_insert_proofs(tree, 14, h, proofs)); + TEST_CHECK((tree == v{ + ah, + ad, eh, + o, o, ef, gh, + o, o, o, o,o, o, g, h})); +} + +TORRENT_TEST(validate_and_insert_proofs_early_success) +{ +// full tree: +// ah +// ad eh +// ab cd ef gh +// a b c d e f g h + + v tree(15); + tree[0] = ah; + tree[1] = ad; + tree[2] = eh; + + v const proofs{f, gh, ad}; + + TEST_CHECK(merkle_validate_and_insert_proofs(tree, 11, e, proofs)); + TEST_CHECK((tree == v{ + ah, + ad, eh, + o, o, ef, gh, + o, o, o, o,e, f, o, o})); +} + +TORRENT_TEST(validate_and_insert_proofs_early_failure) +{ +// full tree: +// ah +// ad eh +// ab cd ef gh +// a b c d e f g h + + v tree(15); + tree[0] = ah; + tree[1] = ad; + tree[2] = ah; // <- this is not the right hash. It should cause validation to fail + + v const proofs{f, gh, ad}; + + TEST_CHECK(!merkle_validate_and_insert_proofs(tree, 11, e, proofs)); + + // make sure tree was correctly restored + TEST_CHECK((tree == v{ + ah, + ad, ah, + o, o, o, o, + o, o, o, o,o, o, o, o})); +} + + +TORRENT_TEST(validate_and_insert_proofs_no_uncles) +{ +// full tree: +// ah +// ad eh +// ab cd ef gh +// a b c d e f g h + + v tree(15); + tree[0] = ah; + + v const proofs; + + TEST_CHECK(!merkle_validate_and_insert_proofs(tree, 1, ad, proofs)); + + // make sure tree was correctly restored + TEST_CHECK((tree == v{ + ah, + o, o, + o, o, o, o, + o, o, o, o,o, o, o, o})); +} + +TORRENT_TEST(validate_and_insert_proofs_root) +{ +// full tree: +// ah +// ad eh +// ab cd ef gh +// a b c d e f g h + + v tree(15); + tree[0] = ah; + + v const proofs; + + // this is just attempting to prove the root, which is ok + TEST_CHECK(merkle_validate_and_insert_proofs(tree, 0, ah, proofs)); + + // nothing happens to the tree in this case, we already had the root + TEST_CHECK((tree == v{ + ah, + o, o, + o, o, o, o, + o, o, o, o,o, o, o, o})); +} + +TORRENT_TEST(validate_and_insert_proofs_root_fail) +{ +// full tree: +// ah +// ad eh +// ab cd ef gh +// a b c d e f g h + + v tree(15); + tree[0] = ah; + + v const proofs; + + // this is just attempting to prove the root, but with the wrong hash + TEST_CHECK(!merkle_validate_and_insert_proofs(tree, 0, a, proofs)); + + // nothing happens to the tree in this case + TEST_CHECK((tree == v{ + ah, + o, o, + o, o, o, o, + o, o, o, o,o, o, o, o})); +} + +TORRENT_TEST(validate_and_insert_proofs_too_many_uncles) +{ +// full tree: +// ah +// ad eh +// ab cd ef gh +// a b c d e f g h + + v tree(15); + tree[0] = ah; + + v const proofs{f, gh, ad, a, b, c , d}; + + TEST_CHECK(merkle_validate_and_insert_proofs(tree, 11, e, proofs)); + TEST_CHECK((tree == v{ + ah, + ad, eh, + o, o, ef, gh, + o, o, o, o,e, f, o, o})); +} diff --git a/test/test_merkle_tree.cpp b/test/test_merkle_tree.cpp new file mode 100644 index 0000000..e4d42f5 --- /dev/null +++ b/test/test_merkle_tree.cpp @@ -0,0 +1,938 @@ +/* + +Copyright (c) 2017, BitTorrent Inc. +Copyright (c) 2019-2020, Steven Siloti +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 + +#include "libtorrent/aux_/merkle.hpp" +#include "libtorrent/aux_/merkle_tree.hpp" +#include "libtorrent/random.hpp" + +#include "test.hpp" +#include "test_utils.hpp" + +using namespace lt; + +namespace { + +int const num_blocks = 259; +auto const f = build_tree(num_blocks); +int const num_leafs = merkle_num_leafs(num_blocks); +int const num_nodes = merkle_num_nodes(num_leafs); +int const num_pad_leafs = num_leafs - num_blocks; + +using verified_t = std::vector; +verified_t const empty_verified(std::size_t(num_blocks), false); + +using s = span; + +span range(std::vector const& c, int first, int count) +{ + return s(c).subspan(first, count); +} + +sha256_hash rand_sha256() +{ + sha256_hash ret; + aux::random_bytes(ret); + return ret; +} + +std::vector build_proof(span tree, int target, int end = 0) +{ + std::vector ret; + while (target > end) + { + ret.push_back(tree[merkle_get_sibling(target)]); + target = merkle_get_parent(target); + } + return ret; +} + +std::vector corrupt(span hashes) +{ + std::vector ret; + ret.reserve(std::size_t(hashes.size())); + std::copy(hashes.begin(), hashes.end(), std::back_inserter(ret)); + ret[146542934 % ret.size()][2] ^= 0x26; + return ret; +} + +std::vector all_set(int count) +{ + return std::vector(std::size_t(count), true); +} + +std::vector none_set(int count) +{ + return std::vector(std::size_t(count), false); +} + +std::vector set_range(std::vector bits, int start, int count) +{ + while (count > 0) + { + TORRENT_ASSERT(start >= 0); + TORRENT_ASSERT(std::size_t(start) < bits.size()); + bits[std::size_t(start)] = true; + ++start; + --count; + } + return bits; +} +} + +TORRENT_TEST(load_tree) +{ + // test with full tree and valid root + { + aux::merkle_tree t(num_blocks, 1, f[0].data()); + t.load_tree(f, empty_verified); + for (int i = 0; i < num_nodes - num_pad_leafs; ++i) + { + TEST_CHECK(t.has_node(i)); + TEST_CHECK(t.compare_node(i, f[i])); + } + for (int i = num_nodes - num_pad_leafs; i < num_nodes; ++i) + { + TEST_CHECK(!t.has_node(i)); + TEST_CHECK(t.compare_node(i, f[i])); + } + } + + // mismatching root hash + { + sha256_hash const bad_root("01234567890123456789012345678901"); + aux::merkle_tree t(num_blocks, 1, bad_root.data()); + t.load_tree(f, empty_verified); + TEST_CHECK(t.has_node(0)); + for (int i = 1; i < num_nodes; ++i) + TEST_CHECK(!t.has_node(i)); + } + + // mismatching size + { + aux::merkle_tree t(num_blocks, 1, f[0].data()); + t.load_tree(span(f).first(f.end_index() - 1), empty_verified); + TEST_CHECK(t.has_node(0)); + for (int i = 1; i < num_nodes; ++i) + TEST_CHECK(!t.has_node(i)); + } +} + +TORRENT_TEST(load_sparse_tree) +{ + // test with full tree and valid root + { + std::vector mask(f.size(), true); + aux::merkle_tree t(num_blocks, 1, f[0].data()); + t.load_sparse_tree(f, mask, empty_verified); + for (int i = 0; i < num_nodes - num_pad_leafs; ++i) + { + TEST_CHECK(t.has_node(i)); + TEST_CHECK(t.compare_node(i, f[i])); + } + for (int i = num_nodes - num_pad_leafs; i < num_nodes; ++i) + { + TEST_CHECK(!t.has_node(i)); + TEST_CHECK(t.compare_node(i, f[i])); + } + } + + // mismatching root hash + { + sha256_hash const bad_root("01234567890123456789012345678901"); + aux::merkle_tree t(num_blocks, 1, bad_root.data()); + std::vector mask(f.size(), false); + mask[1] = true; + mask[2] = true; + t.load_sparse_tree(span(f).subspan(1, 2), mask, empty_verified); + TEST_CHECK(t.has_node(0)); + for (int i = 1; i < num_nodes; ++i) + TEST_CHECK(!t.has_node(i)); + } + + // block layer + { + aux::merkle_tree t(num_blocks, 1, f[0].data()); + int const first_block = merkle_first_leaf(num_leafs); + int const end_block = first_block + num_blocks; + std::vector mask(f.size(), false); + for (int i = first_block; i < end_block; ++i) + mask[std::size_t(i)] = true; + t.load_sparse_tree(span(f).subspan(first_block, num_blocks), mask, empty_verified); + for (int i = 0; i < num_nodes - num_pad_leafs; ++i) + { + TEST_CHECK(t.has_node(i)); + TEST_CHECK(t.compare_node(i, f[i])); + } + for (int i = num_nodes - num_pad_leafs; i < num_nodes; ++i) + { + TEST_CHECK(!t.has_node(i)); + TEST_CHECK(t.compare_node(i, f[i])); + } + } + + // piece layer + { + int const num_pieces = (num_blocks + 1) / 2; + int const first_piece = merkle_first_leaf(merkle_num_leafs(num_pieces)); + aux::merkle_tree t(num_blocks, 2, f[0].data()); + std::vector mask(f.size(), false); + for (int i = first_piece, end = i + num_pieces; i < end; ++i) + mask[std::size_t(i)] = true; + t.load_sparse_tree(span(f).subspan(first_piece, num_pieces), mask, empty_verified); + int const end_piece_layer = first_piece + merkle_num_leafs(num_pieces); + for (int i = 0; i < end_piece_layer; ++i) + { + TEST_CHECK(t.has_node(i)); + TEST_CHECK(t.compare_node(i, f[i])); + } + for (int i = end_piece_layer; i < num_nodes; ++i) + { + TEST_CHECK(!t.has_node(i)); + } + } +} + +namespace { +void test_roundtrip(aux::merkle_tree const& t + , int const block_count + , int const blocks_per_piece) +{ + // TODO: use structured bindings in C++17 + aux::vector mask; + std::vector tree; + std::tie(tree, mask) = t.build_sparse_vector(); + + aux::merkle_tree t2(block_count, blocks_per_piece, f[0].data()); + t2.load_sparse_tree(tree, mask, empty_verified); + + TEST_CHECK(t.build_vector() == t2.build_vector()); + for (int i = 0; i < int(t.size()); ++i) + { + TEST_EQUAL(t[i], t2[i]); + TEST_EQUAL(t.has_node(i), t2.has_node(i)); + + if (!t.has_node(i)) + TEST_CHECK(t[i].is_all_zeros()); + if (!t2.has_node(i)) + TEST_CHECK(t2[i].is_all_zeros()); + + TEST_CHECK(t.compare_node(i, t2[i])); + TEST_CHECK(t2.compare_node(i, t[i])); + } +} +} + +TORRENT_TEST(roundtrip_empty_tree) +{ + aux::merkle_tree t(num_blocks, 1, f[0].data()); + test_roundtrip(t, num_blocks, 1); +} + +TORRENT_TEST(roundtrip_full_tree) +{ + aux::merkle_tree t(num_blocks, 1, f[0].data()); + t.load_tree(f, empty_verified); + test_roundtrip(t, num_blocks, 1); +} + +TORRENT_TEST(roundtrip_piece_layer_tree) +{ + aux::merkle_tree t(num_blocks, 2, f[0].data()); + auto sparse_tree = f; + for (int i = f.end_index() / 2; i < f.end_index(); ++i) + sparse_tree[i] = lt::sha256_hash{}; + t.load_tree(sparse_tree, empty_verified); + test_roundtrip(t, num_blocks, 2); +} + +TORRENT_TEST(roundtrip_partial_tree) +{ + aux::merkle_tree t(num_blocks, 2, f[0].data()); + auto sparse_tree = f; + for (int i = f.end_index() / 4; i < f.end_index(); ++i) + { + if ((i % 3) == 0) + sparse_tree[i] = lt::sha256_hash{}; + } + + t.load_tree(sparse_tree, empty_verified); + test_roundtrip(t, num_blocks, 2); +} + +TORRENT_TEST(roundtrip_more_partial_tree) +{ + aux::merkle_tree t(num_blocks, 2, f[0].data()); + auto sparse_tree = f; + for (int i = f.end_index() / 4; i < f.end_index(); ++i) + { + if ((i % 4) == 0) + sparse_tree[i] = lt::sha256_hash{}; + } + + t.load_tree(sparse_tree, empty_verified); + test_roundtrip(t, num_blocks, 2); +} + +TORRENT_TEST(roundtrip_one_block_tree) +{ + aux::merkle_tree t(1, 256, f[0].data()); + t.load_tree(span(f).first(1), empty_verified); + test_roundtrip(t, 1, 256); +} + +TORRENT_TEST(roundtrip_two_block_tree) +{ + aux::merkle_tree t(2, 256, f[0].data()); + t.load_tree(span(f).first(3), verified_t(std::size_t(2), false)); + test_roundtrip(t, 2, 256); +} + +TORRENT_TEST(roundtrip_two_block_partial_tree) +{ + auto pf = f; + pf.resize(3); + pf[2].clear(); + aux::merkle_tree t(2, 256, f[0].data()); + t.load_tree(pf, verified_t(std::size_t(2), false)); + test_roundtrip(t, 2, 256); +} + +TORRENT_TEST(small_tree) +{ + // a tree with a single block but large piece size + aux::merkle_tree t(1, 256, f[0].data()); + + TEST_CHECK(t.build_vector() == std::vector{f[0]}); +} + +// the 4 layers of the tree: +// 0 +// 1 2 +// 3 4 5 6 +// 7 8 9 10 11 12 13 14 +// 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 + +TORRENT_TEST(sparse_merkle_tree_block_layer) +{ + aux::merkle_tree t(num_blocks, 2, f[0].data()); + + t.load_tree(span(f).first(int(t.size())), empty_verified); + + for (int i = 0; i < int(t.size()); ++i) + TEST_CHECK(t[i] == f[i]); +} + +TORRENT_TEST(get_piece_layer) +{ + // 8 blocks per piece. + aux::merkle_tree t(num_blocks, 8, f[0].data()); + TEST_CHECK(t.verified_leafs() == none_set(num_blocks)); + t.load_tree(span(f).first(int(t.size())), empty_verified); + + int const num_pieces = (num_blocks + 7) / 8; + int const piece_layer_size = merkle_num_leafs(num_pieces); + int const piece_layer_start = merkle_first_leaf(piece_layer_size); + auto const piece_layer = t.get_piece_layer(); + + TEST_CHECK(t.verified_leafs() == all_set(num_blocks)); + + TEST_EQUAL(num_pieces, int(piece_layer.size())); + for (int i = 0; i < int(piece_layer.size()); ++i) + { + TEST_CHECK(t[piece_layer_start + i] == piece_layer[i]); + } +} + +TORRENT_TEST(get_piece_layer_piece_layer_mode) +{ + aux::merkle_tree t(num_blocks, 4, f[0].data()); + int const num_pieces = (num_blocks + 3) / 4; + + // add the entire piece layer + t.load_piece_layer(span(f[127].data(), sha256_hash::size() * num_pieces)); + + int const piece_layer_size = merkle_num_leafs(num_pieces); + int const piece_layer_start = merkle_first_leaf(piece_layer_size); + auto const piece_layer = t.get_piece_layer(); + + TEST_CHECK(t.verified_leafs() == none_set(num_blocks)); + + TEST_EQUAL(num_pieces, int(piece_layer.size())); + for (int i = 0; i < int(piece_layer.size()); ++i) + { + TEST_CHECK(t[piece_layer_start + i] == piece_layer[i]); + } +} + +TORRENT_TEST(merkle_tree_get_hashes) +{ + aux::merkle_tree t(num_blocks, 2, f[0].data()); + + t.load_tree(span(f).first(int(t.size())), empty_verified); + + // all nodes leaf layer + { + auto h = t.get_hashes(0, 0, num_blocks, 0); + TEST_CHECK(s(h) == range(f, 511, num_blocks)); + } + + // all nodes leaf layer but the first + { + auto h = t.get_hashes(0, 1, num_blocks - 1, 0); + TEST_CHECK(s(h) == range(f, 512, num_blocks - 1)); + } + + // all nodes leaf layer but the last + { + auto h = t.get_hashes(0, 0, num_blocks - 1, 0); + TEST_CHECK(s(h) == range(f, 511, num_blocks - 1)); + } + + // one layer up + { + auto h = t.get_hashes(1, 0, 256, 0); + TEST_CHECK(s(h) == range(f, 255, 256)); + } + + // one layer up + one layer proof + { + auto h = t.get_hashes(1, 0, 4, 2); + TEST_CHECK(s(h).first(4) == range(f, 255, 4)); + + // the proof is the sibling to the root of the tree we got back. + // the hashes are rooted at 255 / 2 / 2 = 63 + std::vector const proofs{f[merkle_get_sibling(63)]}; + TEST_CHECK(s(h).subspan(4) == s(proofs)); + } + + // one layer up, hashes 2 - 10, 5 proof layers + { + auto h = t.get_hashes(1, 2, 8, 5); + TEST_CHECK(s(h).first(8) == range(f, 255 + 2, 8)); + + // the proof is the sibling to the root of the tree we got back. + int const start_proofs = merkle_get_parent(merkle_get_parent(merkle_get_parent(257))); + std::vector const proofs{ + f[merkle_get_sibling(start_proofs)] + , f[merkle_get_sibling(merkle_get_parent(start_proofs))] + , f[merkle_get_sibling(merkle_get_parent(merkle_get_parent(start_proofs)))] + }; + TEST_CHECK(s(h).subspan(8) == s(proofs)); + } + + // full tree + { + auto h = t.get_hashes(0, 0, 512, 8); + TEST_CHECK(s(h) == range(f, 511, 512)); + // there won't be any proofs, since we got the full tree + } + + // second half of the tree + { + auto h = t.get_hashes(0, 256, 256, 8); + TEST_CHECK(s(h).first(256) == range(f, 511 + 256, 256)); + + // there just one proof hash + std::vector const proofs{ f[1] }; + TEST_CHECK(s(h).subspan(256) == s(proofs)); + } + + // 3rd quarter of the tree + { + auto h = t.get_hashes(0, 256, 128, 8); + TEST_CHECK(s(h).first(128) == range(f, 511 + 256, 128)); + + // there just two proof hashes + std::vector const proofs{ f[6], f[1] }; + TEST_CHECK(s(h).subspan(128) == s(proofs)); + } + + // 3rd quarter of the tree, starting one layer up + { + auto h = t.get_hashes(1, 128, 64, 7); + TEST_CHECK(s(h).first(64) == range(f, 255 + 128, 64)); + + // still just two proof hashes + std::vector const proofs{ f[6], f[1] }; + TEST_CHECK(s(h).subspan(64) == s(proofs)); + } + + // 3rd quarter of the tree, starting one layer up + // request no proof hashes + { + auto h = t.get_hashes(1, 128, 64, 0); + TEST_CHECK(s(h) == range(f, 255 + 128, 64)); + } +} + +// 0 +// 1 2 +// 3 4 5 6 +// 7 8 9 10 11 12 13 14 +// 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 +// 31 ... 62 +// 63 ... 126 +// 127 ... 254 <- piece layer +// 255 ... 510 +// 511 ... 771 ... padding ... 1022 <- block layer + +using pdiff = piece_index_t::diff_type; + +TORRENT_TEST(add_hashes_full_tree) +{ + for (int blocks_per_piece : {1, 2, 4}) + { + aux::merkle_tree t(num_blocks, blocks_per_piece, f[0].data()); + + // add the entire block layer + auto const result = t.add_hashes(511, pdiff(1), range(f, 511, 512), span()); + + TEST_CHECK(result); + if (!result) return; + + auto const& res = *result; + TEST_EQUAL(res.passed.size(), 0); + TEST_EQUAL(res.failed.size(), 0); + + // check the piece layer + for (int i = 127; i < 255; ++i) + TEST_EQUAL(t[i], f[i]); + + // check the block layer + for (int i = 511; i < 1023; ++i) + TEST_EQUAL(t[i], f[i]); + + TEST_CHECK(t.verified_leafs() == all_set(num_blocks)); + } +} + +TORRENT_TEST(add_hashes_one_piece) +{ + int const blocks_per_piece = 4; + for (int piece_index : {0, 64, 5, 30}) + { + aux::merkle_tree t(num_blocks, blocks_per_piece, f[0].data()); + + int const insert_idx = 127 + piece_index; + auto const result = t.add_hashes(511 + piece_index * blocks_per_piece, pdiff(1) + , range(f, 511 + piece_index * blocks_per_piece, blocks_per_piece) + , build_proof(f, insert_idx)); + + TEST_CHECK(result); + if (!result) return; + + auto const& res = *result; + TEST_EQUAL(res.passed.size(), 0); + TEST_EQUAL(res.failed.size(), 0); + + // the trail of proof hashes + for (int i = insert_idx; i > 0; i = merkle_get_parent(i)) + { + TEST_EQUAL(t[i], f[i]); + TEST_EQUAL(t[merkle_get_sibling(i)], f[merkle_get_sibling(i)]); + } + + // check the piece layer + for (int i = 127; i < 255; ++i) + { + // one is the root of the hashes we added, the other is part of the + // proof anchroing it in the root + if (i == 127 + piece_index || merkle_get_sibling(i) == 127 + piece_index) + TEST_EQUAL(t[i], f[i]); + else + TEST_CHECK(t[i].is_all_zeros()); + } + + // check the block layer + for (int i = 511; i < 1023; ++i) + { + if (i >= 511 + piece_index*blocks_per_piece && i < 511 + piece_index*blocks_per_piece + 4) + TEST_EQUAL(t[i], f[i]); + else + TEST_CHECK(t[i].is_all_zeros()); + } + + int const start_block = piece_index * blocks_per_piece; + int const end_block = std::min(blocks_per_piece, num_blocks - start_block); + TEST_CHECK(t.verified_leafs() == set_range(none_set(num_blocks) + , start_block, end_block)); + } +} + +TORRENT_TEST(add_hashes_one_piece_invalid_proof) +{ + int const blocks_per_piece = 4; + for (int piece_index : {0, 64, 5, 30}) + { + aux::merkle_tree t(num_blocks, blocks_per_piece, f[0].data()); + + int const insert_idx = 127 + piece_index; + auto const result = t.add_hashes(511 + piece_index * blocks_per_piece, pdiff(1) + , range(f, 511 + piece_index * blocks_per_piece, blocks_per_piece) + , corrupt(build_proof(f, insert_idx))); + + TEST_CHECK(!result); + TEST_CHECK(t.verified_leafs() == none_set(num_blocks)); + } +} + +TORRENT_TEST(add_hashes_one_piece_invalid_hash) +{ + int const blocks_per_piece = 4; + for (int piece_index : {0, 64, 5, 30}) + { + aux::merkle_tree t(num_blocks, blocks_per_piece, f[0].data()); + + int const insert_idx = 127 + piece_index; + auto const result = t.add_hashes(511 + piece_index * blocks_per_piece, pdiff(1) + , corrupt(range(f, 511 + piece_index * blocks_per_piece, blocks_per_piece)) + , build_proof(f, insert_idx)); + + TEST_CHECK(!result); + TEST_CHECK(t.verified_leafs() == none_set(num_blocks)); + } +} + +TORRENT_TEST(add_hashes_full_tree_existing_valid_blocks) +{ + for (int piece_index : {0, 63}) + { + for (int blocks_per_piece : {1, 2, 4}) + { + aux::merkle_tree t(num_blocks, blocks_per_piece, f[0].data()); + + for (int i = 511 + piece_index * blocks_per_piece; + i < 511 + std::min(piece_index * blocks_per_piece + 8, num_blocks); + ++i) + { + auto ret = t.set_block(i - 511, f[i]); + TEST_CHECK(std::get<0>(ret) == aux::merkle_tree::set_block_result::unknown); + } + + // add the entire block layer + auto const result = t.add_hashes(511, pdiff(10), range(f, 511, 512), span()); + + TEST_CHECK(result); + if (!result) return; + + auto const& res = *result; + TEST_EQUAL(res.passed.size(), std::size_t(8 / blocks_per_piece)); + TEST_EQUAL(res.failed.size(), 0); + + piece_index_t idx(piece_index + 10); + for (auto passed : res.passed) + { + TEST_EQUAL(passed, idx); + ++idx; + } + + TEST_CHECK(t.verified_leafs() == all_set(num_blocks)); + } + } +} + +TORRENT_TEST(add_hashes_full_tree_existing_invalid_blocks) +{ + for (int piece_index : {0, 63}) + { + std::cout << "piece: " << piece_index << std::endl; + for (int blocks_per_piece : {1, 2, 4}) + { + std::cout << "block per piece: " << blocks_per_piece << std::endl; + aux::merkle_tree t(num_blocks, blocks_per_piece, f[0].data()); + + for (int i = 511 + piece_index * blocks_per_piece; + i < 511 + std::min(piece_index * blocks_per_piece + 8, num_blocks); + ++i) + { + // the hash is invalid + auto ret = t.set_block(i - 511, rand_sha256()); + TEST_CHECK(std::get<0>(ret) == aux::merkle_tree::set_block_result::unknown); + } + + // add the entire block layer + auto const result = t.add_hashes(511, pdiff(10), range(f, 511, 512), span()); + + TEST_CHECK(result); + if (!result) return; + + auto const& res = *result; + TEST_EQUAL(res.passed.size(), 0); + TEST_EQUAL(res.failed.size(), std::size_t(8 / blocks_per_piece)); + + piece_index_t idx(piece_index); + for (auto failed : res.failed) + { + TEST_EQUAL(failed.first, idx + pdiff(10)); + TEST_EQUAL(int(failed.second.size()), std::min(blocks_per_piece + , num_blocks - static_cast(idx) * blocks_per_piece)); + ++idx; + int block = 0; + for (auto const b : failed.second) + { + TEST_EQUAL(b, block); + ++block; + } + } + + TEST_CHECK(t.verified_leafs() == all_set(num_blocks)); + } + } +} + +TORRENT_TEST(set_block_full_block_layer) +{ + int const blocks_per_piece = 4; + aux::merkle_tree t(num_blocks, blocks_per_piece, f[0].data()); + + { + // add the entire block layer + auto const result = t.add_hashes(511, pdiff(1), range(f, 511, 512), span()); + TEST_CHECK(result); + if (!result) return; + } + + TEST_CHECK(t.verified_leafs() == all_set(num_blocks)); + + for (int block = 0; block < num_blocks; ++block) + { + // the tree is complete, we know all hashes already. This is just + // comparing the hash against what we have in the tree + auto const result = t.set_block(block, f[511 + block]); + TEST_CHECK(std::get<0>(result) == aux::merkle_tree::set_block_result::ok); + TEST_EQUAL(std::get<1>(result), block); + TEST_EQUAL(std::get<2>(result), 1); + } +} + +TORRENT_TEST(set_block_invalid_full_block_layer) +{ + int const blocks_per_piece = 4; + aux::merkle_tree t(num_blocks, blocks_per_piece, f[0].data()); + + { + // add the entire block layer + auto const result = t.add_hashes(511, pdiff(1), range(f, 511, 512), span()); + TEST_CHECK(result); + if (!result) return; + } + + TEST_CHECK(t.verified_leafs() == all_set(num_blocks)); + + for (int block = 0; block < num_blocks; ++block) + { + // the tree is complete, we know all hashes already. This is just + // comparing the hash against what we have in the tree + auto const result = t.set_block(block, rand_sha256()); + TEST_CHECK(std::get<0>(result) == aux::merkle_tree::set_block_result::block_hash_failed); + TEST_EQUAL(std::get<1>(result), block); + TEST_EQUAL(std::get<2>(result), 1); + } +} + +TORRENT_TEST(set_block_full_piece_layer) +{ + int const blocks_per_piece = 4; + aux::merkle_tree t(num_blocks, blocks_per_piece, f[0].data()); + + { + // add the entire piece layer + auto const result = t.add_hashes(127, pdiff(1), range(f, 127, 128), span()); + TEST_CHECK(result); + if (!result) return; + } + + for (int block = 0; block < num_blocks; ++block) + { + auto const result = t.set_block(block, f[511 + block]); + if ((block % blocks_per_piece) == blocks_per_piece - 1 || block == num_blocks - 1) + { + TEST_CHECK(std::get<0>(result) == aux::merkle_tree::set_block_result::ok); + TEST_EQUAL(std::get<1>(result), block - (block % blocks_per_piece)); + TEST_EQUAL(std::get<2>(result), blocks_per_piece); + TEST_CHECK(t.verified_leafs() == set_range(none_set(num_blocks), 0, block + 1)); + } + else + { + TEST_CHECK(std::get<0>(result) == aux::merkle_tree::set_block_result::unknown); + TEST_CHECK(t.verified_leafs() == set_range(none_set(num_blocks), 0, block - (block % blocks_per_piece))); + } + } +} + +TORRENT_TEST(set_block_invalid_full_piece_layer) +{ + int const blocks_per_piece = 4; + aux::merkle_tree t(num_blocks, blocks_per_piece, f[0].data()); + + { + // add the entire piece layer + auto const result = t.add_hashes(127, pdiff(1), range(f, 127, 128), span()); + TEST_CHECK(result); + if (!result) return; + } + + for (int block = 0; block < num_blocks; ++block) + { + auto const result = t.set_block(block, rand_sha256()); + if ((block % blocks_per_piece) == blocks_per_piece - 1 || block == num_blocks - 1) + { + TEST_CHECK(std::get<0>(result) == aux::merkle_tree::set_block_result::hash_failed); + TEST_EQUAL(std::get<1>(result), block - (block % blocks_per_piece)); + TEST_EQUAL(std::get<2>(result), blocks_per_piece); + } + else + { + TEST_CHECK(std::get<0>(result) == aux::merkle_tree::set_block_result::unknown); + } + TEST_CHECK(t.verified_leafs() == none_set(num_blocks)); + } +} + +TORRENT_TEST(set_block_empty_tree) +{ + int const blocks_per_piece = 4; + aux::merkle_tree t(num_blocks, blocks_per_piece, f[0].data()); + + for (int block = 0; block < num_blocks - 1; ++block) + { + // the tree is complete, we know all hashes already. This is just + // comparing the hash against what we have in the tree + auto const result = t.set_block(block, f[511 + block]); + TEST_CHECK(std::get<0>(result) == aux::merkle_tree::set_block_result::unknown); + TEST_CHECK(t.verified_leafs() == none_set(num_blocks)); + } + + int const block = num_blocks - 1; + auto const result = t.set_block(block, f[511 + block]); + + TEST_CHECK(std::get<0>(result) == aux::merkle_tree::set_block_result::ok); + TEST_EQUAL(std::get<1>(result), 0); + TEST_EQUAL(std::get<2>(result), num_leafs); + + TEST_CHECK(t.verified_leafs() == all_set(num_blocks)); +} + +TORRENT_TEST(set_block_invalid_empty_tree) +{ + int const blocks_per_piece = 4; + aux::merkle_tree t(num_blocks, blocks_per_piece, f[0].data()); + + for (int block = 0; block < num_blocks; ++block) + { + // the tree is complete, we know all hashes already. This is just + // comparing the hash against what we have in the tree + auto const result = t.set_block(block, rand_sha256()); + TEST_CHECK(std::get<0>(result) == aux::merkle_tree::set_block_result::unknown); + TEST_CHECK(t.verified_leafs() == none_set(num_blocks)); + } +} + +TORRENT_TEST(add_hashes_block_layer_no_padding) +{ + aux::merkle_tree t(num_blocks, 4, f[0].data()); + + auto const result = t.add_hashes(511, pdiff(1), range(f, 511, num_blocks), span()); + + TEST_CHECK(result); + if (!result) return; + + auto const& res = *result; + TEST_EQUAL(res.passed.size(), 0); + TEST_EQUAL(res.failed.size(), 0); + + for (int i = 0; i < 1023; ++i) + TEST_EQUAL(t[i], f[i]); + + TEST_CHECK(t.verified_leafs() == all_set(num_blocks)); +} + +TORRENT_TEST(add_hashes_piece_layer_no_padding) +{ + aux::merkle_tree t(num_blocks, 4, f[0].data()); + + int const num_pieces = (num_blocks + 3) / 4; + auto const result = t.add_hashes(127, pdiff(1), range(f, 127, num_pieces), span()); + + TEST_CHECK(result); + if (!result) return; + + auto const& res = *result; + TEST_EQUAL(res.passed.size(), 0); + TEST_EQUAL(res.failed.size(), 0); + + for (int i = 0; i < 255; ++i) + TEST_EQUAL(t[i], f[i]); + + for (int i = 255; i < 1023; ++i) + TEST_CHECK(t[i].is_all_zeros()); + + TEST_CHECK(t.verified_leafs() == none_set(num_blocks)); +} + +TORRENT_TEST(add_hashes_partial_proofs) +{ + aux::merkle_tree t(num_blocks, 4, f[0].data()); + + // set the first 2 layers + { + auto const result = t.add_hashes(3, pdiff(1), range(f, 3, 4), span()); + TEST_CHECK(result); + if (!result) return; + + for (int i = 0; i < 7; ++i) + TEST_EQUAL(t[i], f[i]); + } + + // use a proof that ties the first piece node 3 (since we don't need it all + // the way to the root). + auto const result = t.add_hashes(127, pdiff(1), range(f, 127, 4), build_proof(f, 31, 3)); + TEST_CHECK(result); + + auto const& res = *result; + TEST_EQUAL(res.passed.size(), 0); + TEST_EQUAL(res.failed.size(), 0); + + for (int i = 127; i < 127 + 4; ++i) + TEST_CHECK(t[i] == f[i]); + + TEST_CHECK(t.verified_leafs() == none_set(num_blocks)); +} + +// TODO: add test for load_piece_layer() +// TODO: add test for add_hashes() with an odd number of blocks +// TODO: add test for set_block() (setting the last block) with an odd number of blocks + diff --git a/test/test_mmap.cpp b/test/test_mmap.cpp new file mode 100644 index 0000000..c62a6d0 --- /dev/null +++ b/test/test_mmap.cpp @@ -0,0 +1,119 @@ +/* + +Copyright (c) 2016, 2019-2020, Arvid Norberg +Copyright (c) 2019, 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_/mmap.hpp" +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + +using namespace lt; +using namespace lt::aux; + +namespace { + +std::vector filled_buffer(std::ptrdiff_t const size) +{ + std::vector buf; + buf.resize(static_cast(size)); + std::uint8_t cnt = 0; + std::generate(buf.begin(), buf.end() + , [&cnt](){ return static_cast(cnt++); }); + return buf; +} +} + +TORRENT_TEST(mmap_read) +{ + std::vector buf = filled_buffer(100); + + { + std::ofstream file("test_file1", std::ios::binary); + file.write(buf.data(), std::streamsize(buf.size())); + } + + auto m = std::make_shared(aux::file_handle("test_file1", 100, open_mode::read_only) + , open_mode::read_only, 100 +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + , std::make_shared() +#endif + ); + + for (auto const i : boost::combine(m->view().range(), buf)) + { + if (boost::get<0>(i) != boost::get<1>(i)) TEST_ERROR("mmap view mismatching"); + } +} + +TORRENT_TEST(mmap_write) +{ + std::vector buf = filled_buffer(100); + + { + auto m = std::make_shared(aux::file_handle("test_file2", 100 + , open_mode::write | open_mode::truncate) + , open_mode::write | open_mode::truncate, 100 +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + , std::make_shared() +#endif + ); + + file_view v = m->view(); + auto range = v.range(); + + std::copy(buf.begin(), buf.end(), range.begin()); + } + + std::ifstream file("test_file2", std::ios_base::binary); + std::vector buf2; + buf2.resize(100); + file.read(buf2.data(), std::streamsize(buf2.size())); + TEST_EQUAL(file.gcount(), 100); + + for (auto const i : boost::combine(buf2, buf)) + { + if (boost::get<0>(i) != boost::get<1>(i)) TEST_ERROR("mmap view mismatching"); + } +} + +#else + +TORRENT_TEST(dummy) {} + +#endif + diff --git a/test/test_natpmp.cpp b/test/test_natpmp.cpp new file mode 100644 index 0000000..3742eed --- /dev/null +++ b/test/test_natpmp.cpp @@ -0,0 +1,169 @@ +/* + +Copyright (c) 2008-2009, 2011-2012, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, 2018, 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/natpmp.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" +#include +#include +#include + +using namespace lt; + +namespace +{ + struct natpmp_callback : aux::portmap_callback + { + virtual ~natpmp_callback() = default; + + void on_port_mapping(port_mapping_t const mapping + , address const& ip, int port + , portmap_protocol const protocol, error_code const& err + , portmap_transport, aux::listen_socket_handle const&) override + { + std::cout + << "mapping: " << mapping + << ", port: " << port + << ", protocol: " << static_cast(protocol) + << ", external-IP: " << print_address(ip) + << ", error: \"" << err.message() << "\"\n"; + } +#ifndef TORRENT_DISABLE_LOGGING + virtual bool should_log_portmap(portmap_transport) const override + { + return true; + } + + virtual void log_portmap(portmap_transport, char const* msg, aux::listen_socket_handle const&) const override + { + std::cout << msg << std::endl; + } +#endif + }; +} + +int main(int argc, char* argv[]) +{ + io_context ios; + std::string user_agent = "test agent"; + + if (argc < 3 || argc > 4) + { + std::cout << "usage: test_natpmp tcp-port udp-port [interface]" << std::endl; + return 1; + } + + error_code ec; + std::vector const routes = lt::enum_routes(ios, ec); + if (ec) + { + std::cerr << "failed to enumerate routes: " << ec.message() << '\n'; + return -1; + } + std::vector const ifs = lt::enum_net_interfaces(ios, ec); + if (ec) + { + std::cerr << "failed to enumerate network interfaces: " << ec.message() << '\n'; + return -1; + } + auto const iface = [&] + { + if (argc > 3) + return std::find_if(ifs.begin(), ifs.end(), [&](ip_interface const& ipf) + { return ipf.name == string_view(argv[3]); }); + else + return std::find_if(ifs.begin(), ifs.end(), [&](ip_interface const& face) + { + if (!face.interface_address.is_v4()) return false; + if (face.interface_address.is_loopback()) 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()) + { + if (argc < 4) + { + std::cerr << "could not find an IPv4 interface to run NAT-PMP test over!\n"; + } + else + { + std::cerr << "could not find interface: \"" << argv[3] << "\"\navailable ones are:\n"; + for (auto const& ipf : ifs) + { + std::cerr << ipf.name << '\n'; + } + } + return -1; + } + + natpmp_callback cb; + auto natpmp_handler = std::make_shared(ios, cb, aux::listen_socket_handle{}); + natpmp_handler->start(*iface); + + deadline_timer timer(ios); + + auto const tcp_map = natpmp_handler->add_mapping(portmap_protocol::tcp + , atoi(argv[1]), tcp::endpoint({}, aux::numeric_cast(atoi(argv[1])))); + natpmp_handler->add_mapping(portmap_protocol::udp, atoi(argv[2]) + , tcp::endpoint({}, aux::numeric_cast(atoi(argv[2])))); + + timer.expires_after(seconds(2)); + timer.async_wait([&] (error_code const&) { ios.io_context::stop(); }); + std::cout << "attempting to map ports TCP: " << argv[1] + << " UDP: " << argv[2] + << " on interface: " << iface->name << std::endl; + + ios.restart(); + ios.run(); + timer.expires_after(seconds(2)); + timer.async_wait([&] (error_code const&) { ios.io_context::stop(); }); + if (tcp_map >= 0) + { + std::cout << "removing mapping " << tcp_map << std::endl; + natpmp_handler->delete_mapping(tcp_map); + } + + ios.restart(); + ios.run(); + natpmp_handler->close(); + + ios.restart(); + ios.run(); + std::cout << "closing" << std::endl; +} diff --git a/test/test_packet_buffer.cpp b/test/test_packet_buffer.cpp new file mode 100644 index 0000000..0398757 --- /dev/null +++ b/test/test_packet_buffer.cpp @@ -0,0 +1,186 @@ +/* + +Copyright (c) 2013, 2015-2017, 2019, Arvid Norberg +Copyright (c) 2018, 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/aux_/packet_buffer.hpp" +#include "libtorrent/aux_/packet_pool.hpp" + +using lt::aux::packet_buffer; +using lt::aux::packet_ptr; +using lt::aux::packet_pool; +using lt::aux::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..ad88fbb --- /dev/null +++ b/test/test_part_file.cpp @@ -0,0 +1,258 @@ +/* + +Copyright (c) 2014-2019, Arvid Norberg +Copyright (c) 2018, 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 "test.hpp" +#include "test_utils.hpp" +#include "libtorrent/part_file.hpp" +#include "libtorrent/aux_/posix_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, 10_piece, 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, 10_piece, 0, ec); + if (ec) std::printf("part_file::readv: %s\n", ec.message().c_str()); + + for (int i = 0; i < int(buf.size()); ++i) + TEST_CHECK(buf[std::size_t(i)] == char(i)); + + sha1_hash const cmp_hash = hasher(buf).final(); + + hasher ph; + pf.hashv(ph, sizeof(buf), 10_piece, 0, ec); + if (ec) std::printf("part_file::hashv: %s\n", ec.message().c_str()); + + TEST_CHECK(ph.final() == cmp_hash); + } + + { + // 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, 10_piece, 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(10_piece); + + 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()); + } +} + +TORRENT_TEST(posix_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()); + + aux::posix_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, 10_piece, 0, ec); + if (ec) std::printf("posix_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, 10_piece, 0, ec); + if (ec) std::printf("posix_part_file::readv: %s\n", ec.message().c_str()); + + for (int i = 0; i < int(buf.size()); ++i) + TEST_CHECK(buf[std::size_t(i)] == char(i)); + + sha1_hash const cmp_hash = hasher(buf).final(); + + hasher ph; + pf.hashv(ph, sizeof(buf), 10_piece, 0, ec); + if (ec) std::printf("posix_part_file::hashv: %s\n", ec.message().c_str()); + + TEST_CHECK(ph.final() == cmp_hash); + } + + { + // load the part file back in + aux::posix_part_file pf(combine_path(cwd, "partfile_test_dir2"), "partfile.parts", 100, piece_size); + + buf.fill(0); + + iovec_t v = buf; + pf.readv(v, 10_piece, 0, ec); + if (ec) std::printf("posix_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(10_piece); + + 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..cbc7152 --- /dev/null +++ b/test/test_pe_crypto.cpp @@ -0,0 +1,163 @@ +/* + +Copyright (c) 2007, Un Shyam +Copyright (c) 2007-2008, 2011, 2013-2019, Arvid Norberg +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2018, 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 "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..1a0c8a0 --- /dev/null +++ b/test/test_peer_classes.cpp @@ -0,0 +1,151 @@ +/* + +Copyright (c) 2014-2017, 2019, Arvid Norberg +Copyright (c) 2018, 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/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(make_address_v4("200.1.1.0") + , make_address_v4("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..d2771bf --- /dev/null +++ b/test/test_peer_list.cpp @@ -0,0 +1,976 @@ +/* + +Copyright (c) 2014-2019, Arvid Norberg +Copyright (c) 2016, 2018, 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/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.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..afb426c --- /dev/null +++ b/test/test_peer_priority.cpp @@ -0,0 +1,99 @@ +/* + +Copyright (c) 2012-2017, 2019-2020, Arvid Norberg +Copyright (c) 2018, 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/peer_list.hpp" +#include "libtorrent/hasher.hpp" +#include "setup_transfer.hpp" // for supports_ipv6() +#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_piece_picker.cpp b/test/test_piece_picker.cpp new file mode 100644 index 0000000..56c1857 --- /dev/null +++ b/test/test_piece_picker.cpp @@ -0,0 +1,2809 @@ +/* + +Copyright (c) 2005, 2007-2010, 2012-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016, 2018, 2020, Alden Torres +Copyright (c) 2019, 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/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; +const int default_piece_size = blocks_per_piece * default_block_size; + +typed_bitfield string2vec(char const* have_str) +{ + const int num_pieces = int(strlen(have_str)); + typed_bitfield have(num_pieces, false); + for (auto i = 0_piece; 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 const piece_size = default_piece_size) +{ + const int num_pieces = int(strlen(availability)); + TORRENT_ASSERT(int(strlen(have_str)) == num_pieces); + + auto p = std::make_shared( + std::int64_t(num_pieces) * piece_size, piece_size); + + for (auto i = 0_piece; 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 (auto i = 0_piece; 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 (auto i = 0_piece; 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); + p->set_piece_priority(i, prio); + + TEST_CHECK(p->piece_priority(i) == prio); + } + + for (auto i = 0_piece; i < piece_index_t(num_pieces); ++i) + { + if (!have[i]) continue; + p->we_have(i); + for (int j = 0; j < blocks_per_piece; ++j) + TEST_CHECK(p->is_finished(piece_block(i, j))); + } + + aux::vector availability_vec; + p->get_availability(availability_vec); + for (auto i = 0_piece; 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({0_piece, 0}) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0_piece, 0)) != picked.end()); + + p->abort_download({0_piece, 0}, tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested({0_piece, 0}) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0_piece, 0)) != picked.end()); + + p->mark_as_downloading({0_piece, 0}, &tmp1); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested({0_piece, 0}) == true); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0_piece, 0)) == picked.end()); + + p->abort_download({0_piece, 0}, tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested({0_piece, 0}) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0_piece, 0)) != picked.end()); + + p->mark_as_downloading({0_piece, 0}, &tmp1); + p->mark_as_downloading({0_piece, 1}, &tmp1); + p->abort_download({0_piece, 0}, tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested({0_piece, 0}) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0_piece, 0)) != picked.end()); + + p->mark_as_downloading({0_piece, 0}, &tmp1); + p->mark_as_writing({0_piece, 0}, &tmp1); + p->write_failed({0_piece, 0}); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(1_piece, 0)) != picked.end() + || std::find(picked.begin(), picked.end(), piece_block(2_piece, 0)) != picked.end()); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0_piece, 0)) == picked.end()); + p->restore_piece(0_piece); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested({0_piece, 0}) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0_piece, 0)) != picked.end()); + + p->mark_as_downloading({0_piece, 0}, &tmp1); + p->mark_as_writing({0_piece, 0}, &tmp1); + p->mark_as_finished({0_piece, 0}, &tmp1); + p->abort_download({0_piece, 0}, tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested({0_piece, 0}) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0_piece, 0)) == picked.end()); +} + +TORRENT_TEST(abort_download2) +{ + auto p = setup_picker("1111111", " ", "7110000", ""); + piece_picker::downloading_piece st; + p->mark_as_downloading({0_piece, 0}, &tmp1); + p->mark_as_finished({0_piece, 1}, nullptr); + p->piece_info(0_piece, st); + TEST_EQUAL(st.requested, 1); + TEST_EQUAL(st.finished, 1); + p->abort_download({0_piece, 0}, tmp_peer); + p->piece_info(0_piece, 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({0_piece, 0}) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0_piece, 0)) != picked.end()); +} + +TORRENT_TEST(get_downloaders) +{ + auto p = setup_picker("1111111", " ", "7110000", ""); + + p->mark_as_downloading({0_piece, 2}, &tmp1); + p->mark_as_writing({0_piece, 2}, &tmp1); + p->abort_download({0_piece, 2}, &tmp1); + p->mark_as_downloading({0_piece, 2}, &tmp2); + p->mark_as_writing({0_piece, 2}, &tmp2); + + { + std::vector d = p->get_downloaders(0_piece); + 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({0_piece, 3}, &tmp1); + p->abort_download({0_piece, 3}, &tmp1); + p->mark_as_downloading({0_piece, 3}, &tmp2); + p->mark_as_writing({0_piece, 3}, &tmp2); + + { + std::vector d = p->get_downloaders(0_piece); + + 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 + { + std::vector d = p->get_downloaders(1_piece); + + 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 (auto i = 0_piece; i < 4_piece; ++i) + for (int k = 0; k < blocks_per_piece; ++k) + p->mark_as_downloading(piece_block(i, k), &tmp1); + + p->mark_as_downloading({0_piece, 0}, &tmp2); + + std::printf("num_peers: %d\n", p->num_peers({0_piece, 0})); + TEST_EQUAL(p->num_peers({0_piece, 0}), 2); + + p->abort_download({0_piece, 0}, &tmp1); + + std::printf("num_peers: %d\n", p->num_peers({0_piece, 0})); + TEST_EQUAL(p->num_peers({0_piece, 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) == 1_piece); +} + +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) == 5_piece); + + p = setup_picker("1111111", " ", "1171121", ""); + TEST_CHECK(test_pick(p) == 2_piece); + + p = setup_picker("1111111", " ", "1131521", ""); + TEST_CHECK(test_pick(p) == 4_piece); +} + +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) + == 2_piece); +} + +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, 2_piece); + + 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) == 2_piece); + TEST_CHECK(test_pick(p, piece_picker::rarest_first | piece_picker::reverse) == 2_piece); + TEST_CHECK(test_pick(p, piece_picker::sequential) == 2_piece); + TEST_CHECK(test_pick(p, piece_picker::sequential | piece_picker::reverse) == 2_piece); +} + +TORRENT_TEST(we_dont_have) +{ + // make sure we_dont_have works + auto p = setup_picker("1111111", "*******", "0100000", ""); + TEST_CHECK(p->have_piece(1_piece)); + TEST_CHECK(p->have_piece(2_piece)); + p->we_dont_have(1_piece); + p->we_dont_have(2_piece); + TEST_CHECK(!p->have_piece(1_piece)); + TEST_CHECK(!p->have_piece(2_piece)); + auto picked = pick_pieces(p, "*** ** ", 1, 0, nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front().piece_index == 1_piece); +} + +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[0_piece] != 0); + TEST_CHECK(avail[1_piece] != 0); + TEST_CHECK(avail[2_piece] != 0); + TEST_CHECK(avail[3_piece] != 0); + TEST_CHECK(avail[4_piece] != 0); + + p->dec_refcount(3_piece, nullptr); + + p->get_availability(avail); + TEST_EQUAL(avail.size(), 7); + + TEST_CHECK(avail[0_piece] != 0); + TEST_CHECK(avail[1_piece] != 0); + TEST_CHECK(avail[2_piece] != 0); + TEST_CHECK(avail[3_piece] == 0); + TEST_CHECK(avail[4_piece] != 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(0_piece, 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(0_piece); + + TEST_EQUAL(p->want().num_pieces, 6); + TEST_EQUAL(p->have_want().num_pieces, 0); + TEST_EQUAL(p->have().num_pieces, 1); + + p->resize(28 * default_piece_size, default_piece_size); + TEST_EQUAL(p->piece_priority(0_piece), dont_download); + TEST_EQUAL(p->want().num_pieces, 28 - 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_bytes, 0); + TEST_EQUAL(p->want().last_piece, true); + + TEST_EQUAL(p->have_want().num_pieces, 7); + TEST_EQUAL(p->have_want().pad_bytes, 0); + TEST_EQUAL(p->have_want().last_piece, true); + + TEST_EQUAL(p->have().num_pieces, 7); + TEST_EQUAL(p->have().pad_bytes, 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(2_piece, 2) + || picked.front() == piece_block(2_piece, 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(3_piece, 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(1_piece, 1) + || picked.front() == piece_block(1_piece, 2) + || picked.front() == piece_block(1_piece, 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(1_piece, 1) + || picked.front() == piece_block(2_piece, 2) + || picked.front() == piece_block(3_piece, 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(1_piece, 1) + || picked.front() == piece_block(2_piece, 2) + || picked.front() == piece_block(3_piece, 3)); +} + +TORRENT_TEST(prefer_contiguous_no_duplicates) +{ + // this exercises the case where we expand a piece that we selected (since + // prefer contiguous is 8), but still want to pick more pieces afterwards. + // We make sure we don't pick any of the pieces we expanded into + auto p = setup_picker("1111111", " ", "", ""); + auto picked = pick_pieces(p, " *** ", 32, 8, nullptr + , piece_picker::rarest_first, empty_vector); + TEST_EQUAL(int(picked.size()), 3 * blocks_per_piece); + print_pick(picked); + TEST_CHECK(verify_pick(p, picked, true)); +} + +TORRENT_TEST(prefer_contiguous_suggested) +{ + // this exercises the case where we expand a piece that we selected (since + // prefer contiguous > 0) but need to ignore the suggested piece, since it + // was picked first + auto p = setup_picker("1111111", " ", "", ""); + std::vector const suggested_pieces = { 3_piece }; + auto picked = pick_pieces(p, " *** ", 32, 32, nullptr + , piece_picker::rarest_first, suggested_pieces); + + TEST_EQUAL(int(picked.size()), 3 * blocks_per_piece); + print_pick(picked); + TEST_CHECK(verify_pick(p, picked, true)); +} + +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 == 3_piece || picked[std::size_t(i)].piece_index == 5_piece); + + 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 (auto i = 0_piece; i < 7_piece; ++i) + { + TEST_EQUAL(p->cursor(), i); + TEST_EQUAL(p->reverse_cursor(), 7_piece); + p->we_have(i); + } + TEST_CHECK(p->is_finished()); + TEST_CHECK(p->is_seeding()); + TEST_EQUAL(p->cursor(), 7_piece); + TEST_EQUAL(p->reverse_cursor(), 0_piece); +} + +TORRENT_TEST(cursors_sweep_up_set_piece_priority) +{ + // sweep up, set_piece_priority() + auto p = setup_picker("7654321", " ", "", ""); + for (auto i = 0_piece; i < 7_piece; ++i) + { + TEST_EQUAL(p->cursor(), i); + TEST_EQUAL(p->reverse_cursor(), 7_piece); + p->set_piece_priority(i, dont_download); + } + TEST_CHECK(p->is_finished()); + TEST_CHECK(!p->is_seeding()); + TEST_EQUAL(p->cursor(), 7_piece); + TEST_EQUAL(p->reverse_cursor(), 0_piece); +} + +TORRENT_TEST(cursors_sweep_down_we_have) +{ + // sweep down, we_have() + auto p = setup_picker("7654321", " ", "", ""); + for (auto i = 6_piece; i >= 0_piece; --i) + { + TEST_EQUAL(p->cursor(), 0_piece); + 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(), 7_piece); + TEST_EQUAL(p->reverse_cursor(), 0_piece); +} + +TORRENT_TEST(cursors_sweep_down_set_piece_priority) +{ + // sweep down, set_piece_priority() + auto p = setup_picker("7654321", " ", "", ""); + for (auto i = 6_piece; i >= 0_piece; --i) + { + TEST_EQUAL(p->cursor(), 0_piece); + 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(), 7_piece); + TEST_EQUAL(p->reverse_cursor(), 0_piece); +} + +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 <= 3_piece + && right >= 3_piece; ++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(), 7_piece); + TEST_EQUAL(p->reverse_cursor(), 0_piece); +} + +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 <= 3_piece + && right >= 3_piece; ++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(), 7_piece); + TEST_EQUAL(p->reverse_cursor(), 0_piece); +} + +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(), 7_piece); + TEST_EQUAL(p->reverse_cursor(), 0_piece); + for (auto i = 0_piece; i < 7_piece; ++i) + { + p->we_dont_have(i); + TEST_EQUAL(p->cursor(), 0_piece); + TEST_EQUAL(p->reverse_cursor(), next(i)); + } + TEST_CHECK(!p->is_finished()); + TEST_CHECK(!p->is_seeding()); + TEST_EQUAL(p->cursor(), 0_piece); + TEST_EQUAL(p->reverse_cursor(), 7_piece); +} + +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(), 7_piece); + TEST_EQUAL(p->reverse_cursor(), 0_piece); + for (auto i = 6_piece; i >= 0_piece; --i) + { + p->we_dont_have(i); + TEST_EQUAL(p->cursor(), i); + TEST_EQUAL(p->reverse_cursor(), 7_piece); + } + TEST_CHECK(!p->is_finished()); + TEST_CHECK(!p->is_seeding()); + TEST_EQUAL(p->cursor(), 0_piece); + TEST_EQUAL(p->reverse_cursor(), 7_piece); +} + +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(), 7_piece); + TEST_EQUAL(p->reverse_cursor(), 0_piece); + for (auto left = 3_piece, right = 3_piece; + left >= 0_piece && right < 7_piece; + --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(), 0_piece); + TEST_EQUAL(p->reverse_cursor(), 7_piece); +} + +TORRENT_TEST(cursors) +{ + // test cursors + auto p = setup_picker("7654321", " ", "", ""); + TEST_EQUAL(p->cursor(), 0_piece); + TEST_EQUAL(p->reverse_cursor(), 7_piece); + p->we_have(1_piece); + TEST_EQUAL(p->cursor(), 0_piece); + TEST_EQUAL(p->reverse_cursor(), 7_piece); + p->we_have(0_piece); + TEST_EQUAL(p->cursor(), 2_piece); + TEST_EQUAL(p->reverse_cursor(), 7_piece); + p->we_have(5_piece); + TEST_EQUAL(p->cursor(), 2_piece); + TEST_EQUAL(p->reverse_cursor(), 7_piece); + p->we_have(6_piece); + TEST_EQUAL(p->cursor(), 2_piece); + TEST_EQUAL(p->reverse_cursor(), 5_piece); + p->we_have(4_piece); + p->we_have(3_piece); + p->we_have(2_piece); + TEST_EQUAL(p->cursor(), 7_piece); + TEST_EQUAL(p->reverse_cursor(), 0_piece); + + p = setup_picker("7654321", " ", "", ""); + TEST_EQUAL(p->cursor(), 0_piece); + TEST_EQUAL(p->reverse_cursor(), 7_piece); + p->set_piece_priority(1_piece, dont_download); + TEST_EQUAL(p->cursor(), 0_piece); + TEST_EQUAL(p->reverse_cursor(), 7_piece); + p->set_piece_priority(0_piece, dont_download); + TEST_EQUAL(p->cursor(), 2_piece); + TEST_EQUAL(p->reverse_cursor(), 7_piece); + p->set_piece_priority(5_piece, dont_download); + TEST_EQUAL(p->cursor(), 2_piece); + TEST_EQUAL(p->reverse_cursor(), 7_piece); + p->set_piece_priority(6_piece, dont_download); + TEST_EQUAL(p->cursor(), 2_piece); + TEST_EQUAL(p->reverse_cursor(), 5_piece); + p->set_piece_priority(4_piece, dont_download); + p->set_piece_priority(3_piece, dont_download); + p->set_piece_priority(2_piece, dont_download); + TEST_EQUAL(p->cursor(), 7_piece); + TEST_EQUAL(p->reverse_cursor(), 0_piece); + p->set_piece_priority(3_piece, low_priority); + TEST_EQUAL(p->cursor(), 3_piece); + TEST_EQUAL(p->reverse_cursor(), 4_piece); +} + +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(0_piece, dont_download); + TEST_EQUAL(p->want().num_pieces, 6); + TEST_EQUAL(p->have_want().num_pieces, 0); + p->mark_as_finished({0_piece, 0}, nullptr); + p->we_have(0_piece); + 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(0_piece); + p->set_piece_priority(0_piece, 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(0_piece); + p->set_piece_priority(0_piece, dont_download); + p->set_piece_priority(0_piece, low_priority); + p->set_piece_priority(0_piece, dont_download); + + std::vector prios; + p->piece_priorities(prios); + TEST_CHECK(prios.size() == 7); + std::vector const prio_comp{0_pri, 6_pri, 5_pri, 4_pri, 3_pri, 2_pri, 1_pri}; + TEST_CHECK(prios == prio_comp); +} + +TORRENT_TEST(restore_piece) +{ + // test restore_piece + auto p = setup_picker("1234567", " ", "", ""); + p->mark_as_finished({0_piece, 0}, nullptr); + p->mark_as_finished({0_piece, 1}, nullptr); + p->mark_as_finished({0_piece, 2}, nullptr); + p->mark_as_finished({0_piece, 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 == 1_piece); + + p->restore_piece(0_piece); + picked = pick_pieces(p, "*******", 1, 0, nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 1); + TEST_CHECK(picked.front().piece_index == 0_piece); + + p->mark_as_finished({0_piece, 0}, nullptr); + p->mark_as_finished({0_piece, 1}, nullptr); + p->mark_as_finished({0_piece, 2}, nullptr); + p->mark_as_finished({0_piece, 3}, nullptr); + p->set_piece_priority(0_piece, dont_download); + + picked = pick_pieces(p, "*******", 1, 0, nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 1); + TEST_CHECK(picked.front().piece_index == 1_piece); + + p->restore_piece(0_piece); + picked = pick_pieces(p, "*******", 1, 0, nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 1); + TEST_CHECK(picked.front().piece_index == 1_piece); + + p->set_piece_priority(0_piece, top_priority); + picked = pick_pieces(p, "*******", 1, 0, nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 1); + TEST_CHECK(picked.front().piece_index == 0_piece); +} + +TORRENT_TEST(restore_piece_finished_blocks) +{ + // test restore_piece with a list of blocks to reset, not the whole piece + auto p = setup_picker("1234567", " ", "", ""); + p->mark_as_finished({0_piece, 0}, nullptr); + p->mark_as_finished({0_piece, 1}, nullptr); + p->mark_as_finished({0_piece, 2}, nullptr); + p->mark_as_finished({0_piece, 3}, nullptr); + + TEST_CHECK(p->is_finished(piece_block(0_piece, 0))); + TEST_CHECK(p->is_finished(piece_block(0_piece, 1))); + TEST_CHECK(p->is_finished(piece_block(0_piece, 2))); + TEST_CHECK(p->is_finished(piece_block(0_piece, 3))); + + { + auto const dl = p->get_download_queue(); + TEST_EQUAL(dl.size(), 1); + auto const 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_finished); + TEST_EQUAL(blocks[2].state, piece_picker::block_info::state_finished); + TEST_EQUAL(blocks[3].state, piece_picker::block_info::state_finished); + } + + // this should only restore block 1 and 2 + p->restore_piece(0_piece, std::vector{1, 2}); + + TEST_CHECK(p->is_finished(piece_block(0_piece, 0))); + TEST_CHECK(!p->is_finished(piece_block(0_piece, 1))); + TEST_CHECK(!p->is_finished(piece_block(0_piece, 2))); + TEST_CHECK(p->is_finished(piece_block(0_piece, 3))); + + { + auto const dl = p->get_download_queue(); + TEST_EQUAL(dl.size(), 1); + auto const 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_finished); + } +} + +TORRENT_TEST(restore_piece_downloading_blocks) +{ + // test restore_piece with a list of blocks to reset, not the whole piece + auto p = setup_picker("1234567", " ", "", ""); + p->mark_as_writing({0_piece, 0}, nullptr); + p->mark_as_writing({0_piece, 1}, nullptr); + p->mark_as_writing({0_piece, 2}, nullptr); + p->mark_as_writing({0_piece, 3}, nullptr); + + TEST_CHECK(p->is_downloaded(piece_block(0_piece, 0))); + TEST_CHECK(p->is_downloaded(piece_block(0_piece, 1))); + TEST_CHECK(p->is_downloaded(piece_block(0_piece, 2))); + TEST_CHECK(p->is_downloaded(piece_block(0_piece, 3))); + + { + auto const dl = p->get_download_queue(); + TEST_EQUAL(dl.size(), 1); + auto const blocks = p->blocks_for_piece(dl[0]); + TEST_EQUAL(blocks[0].state, piece_picker::block_info::state_writing); + TEST_EQUAL(blocks[1].state, piece_picker::block_info::state_writing); + TEST_EQUAL(blocks[2].state, piece_picker::block_info::state_writing); + TEST_EQUAL(blocks[3].state, piece_picker::block_info::state_writing); + } + + // this should only restore block 1 and 2 + p->restore_piece(0_piece, std::vector{1, 2}); + + TEST_CHECK(p->is_downloaded(piece_block(0_piece, 0))); + TEST_CHECK(!p->is_downloaded(piece_block(0_piece, 1))); + TEST_CHECK(!p->is_downloaded(piece_block(0_piece, 2))); + TEST_CHECK(p->is_downloaded(piece_block(0_piece, 3))); + + { + auto const dl = p->get_download_queue(); + TEST_EQUAL(dl.size(), 1); + auto const blocks = p->blocks_for_piece(dl[0]); + TEST_EQUAL(blocks[0].state, piece_picker::block_info::state_writing); + 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_writing); + } +} + +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({2_piece, 2}, &tmp1); + p->mark_as_downloading({1_piece, 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 == 1_piece); + + // don't pick locked pieces + picked.clear(); + p->lock_piece(1_piece); + 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 == 2_piece); + + p->restore_piece(1_piece); + p->mark_as_downloading({2_piece, 0}, &tmp1); + p->mark_as_downloading({2_piece, 1}, &tmp1); + p->mark_as_downloading({2_piece, 3}, &tmp1); + p->mark_as_downloading({1_piece, 0}, &tmp1); + p->mark_as_downloading({1_piece, 1}, &tmp1); + p->mark_as_downloading({1_piece, 2}, &tmp1); + p->mark_as_downloading({1_piece, 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({0_piece, 0}, &tmp1); + p->mark_as_downloading({0_piece, 1}, &tmp2); + p->mark_as_downloading({0_piece, 2}, &tmp3); + p->mark_as_downloading({1_piece, 1}, &tmp1); + p->mark_as_downloading({2_piece, 1}, &tmp2); + p->mark_as_downloading({3_piece, 1}, &tmp3); + + std::vector const expected_dls1{&tmp1, &tmp2, &tmp3, nullptr}; + std::vector const expected_dls2{nullptr, &tmp1, nullptr, nullptr}; + std::vector const expected_dls3{nullptr, &tmp2, nullptr, nullptr}; + std::vector const expected_dls4{nullptr, &tmp3, nullptr, nullptr}; + std::vector const expected_dls5{&tmp1, nullptr, &tmp3, nullptr}; + std::vector dls = p->get_downloaders(0_piece); + TEST_CHECK(dls == expected_dls1); + dls = p->get_downloaders(1_piece); + TEST_CHECK(dls == expected_dls2); + dls = p->get_downloaders(2_piece); + TEST_CHECK(dls == expected_dls3); + dls = p->get_downloaders(3_piece); + TEST_CHECK(dls == expected_dls4); + + p->clear_peer(&tmp2); + dls = p->get_downloaders(0_piece); + TEST_CHECK(dls == 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(0_piece, &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) == 2_piece); +} + +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) == 1_piece); +} + +TORRENT_TEST(inc_ref_dec_ref) +{ + // test inc_ref and dec_ref + auto p = setup_picker("1233333", " * ", "", ""); + TEST_CHECK(test_pick(p) == 0_piece); + + p->dec_refcount(0_piece, &tmp0); + TEST_CHECK(test_pick(p) == 1_piece); + + p->dec_refcount(4_piece, &tmp0); + p->dec_refcount(4_piece, &tmp1); + TEST_CHECK(test_pick(p) == 4_piece); + + // decrease refcount on something that's not in the piece list + p->dec_refcount(5_piece, &tmp0); + p->inc_refcount(5_piece, &tmp0); + + typed_bitfield bits = string2vec("* "); + TEST_EQUAL(bits.get_bit(0_piece), true); + TEST_EQUAL(bits.get_bit(1_piece), false); + TEST_EQUAL(bits.get_bit(2_piece), false); + TEST_EQUAL(bits.get_bit(3_piece), false); + TEST_EQUAL(bits.get_bit(4_piece), false); + TEST_EQUAL(bits.get_bit(5_piece), false); + TEST_EQUAL(bits.get_bit(6_piece), false); + p->inc_refcount(bits, &tmp0); + bits = string2vec(" * "); + + TEST_EQUAL(bits.get_bit(0_piece), false); + TEST_EQUAL(bits.get_bit(1_piece), false); + TEST_EQUAL(bits.get_bit(2_piece), false); + TEST_EQUAL(bits.get_bit(3_piece), false); + TEST_EQUAL(bits.get_bit(4_piece), true); + TEST_EQUAL(bits.get_bit(5_piece), false); + TEST_EQUAL(bits.get_bit(6_piece), false); + p->dec_refcount(bits, &tmp2); + TEST_EQUAL(test_pick(p), 0_piece); +} + +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({2_piece, 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(2_piece, 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); + std::set const expected_pieces{4_piece,5_piece,6_piece,7_piece}; + TEST_CHECK(picked_pieces == expected_pieces); +} + +TORRENT_TEST(parole_mode) +{ + // test parole mode + auto p = setup_picker("3333133", " ", "", ""); + p->mark_as_finished({0_piece, 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(0_piece, 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(4_piece, 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(1_piece, i)); + p->set_piece_priority(0_piece, dont_download); + p->set_piece_priority(1_piece, dont_download); + p->set_piece_priority(2_piece, dont_download); + p->set_piece_priority(3_piece, 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(5_piece, 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(5_piece, 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(3_piece, &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({0_piece, 0}, &tmp1 + , piece_picker::reverse); + + TEST_EQUAL(test_pick(p, piece_picker::rarest_first), 1_piece); + + // make sure another reversed peer pick the same piece + TEST_EQUAL(test_pick(p, piece_picker::rarest_first | piece_picker::reverse), 0_piece); +} + +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({0_piece, 1}, &tmp1 + , piece_picker::reverse); + TEST_EQUAL(test_pick(p), 1_piece); + + // 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({0_piece, 2}, &tmp1); + TEST_EQUAL(test_pick(p), 0_piece); +} + +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({0_piece, 1}, &tmp1); + + // a reversed peer picked a block from piece 0 + // This should make the piece reversed + p->mark_as_downloading({0_piece, 0}, &tmp1 + , piece_picker::reverse); + + TEST_EQUAL(test_pick(p, piece_picker::rarest_first | piece_picker::reverse), 0_piece); +} + +TORRENT_TEST(piece_stats) +{ + auto p = setup_picker("3456789", "* ", "", "0300000"); + + piece_picker::piece_stats_t stat = p->piece_stats(0_piece); + TEST_EQUAL(stat.peer_count, 3); + TEST_EQUAL(stat.have, 1); + TEST_EQUAL(stat.downloading, 0); + + stat = p->piece_stats(1_piece); + 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(0_piece), true); + TEST_EQUAL(p->has_piece_passed(1_piece), false); + TEST_EQUAL(p->num_passed(), 1); + TEST_EQUAL(p->have().num_pieces, 1); + + p->piece_passed(1_piece); + TEST_EQUAL(p->num_passed(), 2); + TEST_EQUAL(p->have().num_pieces, 1); + + p->we_have(1_piece); + TEST_EQUAL(p->have().num_pieces, 2); + + p->mark_as_finished({2_piece, 0}, &tmp1); + p->piece_passed(2_piece); + 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({2_piece, 1}, &tmp1); + p->mark_as_finished({2_piece, 2}, &tmp1); + p->mark_as_finished({2_piece, 3}, &tmp1); + TEST_EQUAL(p->have().num_pieces, 3); + TEST_EQUAL(p->have_piece(2_piece), true); +} + +TORRENT_TEST(piece_passed_causing_we_have) +{ + auto p = setup_picker("1111111", "* ", "", "0700000"); + + TEST_EQUAL(p->has_piece_passed(0_piece), true); + TEST_EQUAL(p->has_piece_passed(1_piece), false); + TEST_EQUAL(p->num_passed(), 1); + TEST_EQUAL(p->have().num_pieces, 1); + + p->mark_as_finished({1_piece, 3}, &tmp1); + TEST_EQUAL(p->num_passed(), 1); + TEST_EQUAL(p->have().num_pieces, 1); + + p->piece_passed(1_piece); + 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(0_piece).peer_count, 3); + + p->dec_refcount(0_piece, &tmp1); + + TEST_EQUAL(p->piece_stats(0_piece).peer_count, 2); + TEST_EQUAL(p->piece_stats(1_piece).peer_count, 3); + TEST_EQUAL(p->piece_stats(2_piece).peer_count, 3); + TEST_EQUAL(p->piece_stats(3_piece).peer_count, 3); +} + +TORRENT_TEST(we_dont_have2) +{ + auto p = setup_picker("1111111", "* * ", "1101111", ""); + TEST_EQUAL(p->has_piece_passed(0_piece), true); + TEST_EQUAL(p->has_piece_passed(1_piece), false); + TEST_EQUAL(p->has_piece_passed(2_piece), 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(0_piece); + + TEST_EQUAL(p->has_piece_passed(0_piece), false); + TEST_EQUAL(p->has_piece_passed(1_piece), false); + TEST_EQUAL(p->has_piece_passed(2_piece), 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(0_piece), true); + TEST_EQUAL(p->has_piece_passed(1_piece), false); + TEST_EQUAL(p->has_piece_passed(2_piece), 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(2_piece); + + TEST_EQUAL(p->has_piece_passed(0_piece), true); + TEST_EQUAL(p->has_piece_passed(1_piece), false); + TEST_EQUAL(p->has_piece_passed(2_piece), 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(0_piece), true); + TEST_EQUAL(p->has_piece_passed(1_piece), false); + TEST_EQUAL(p->have_piece(0_piece), true); + TEST_EQUAL(p->have_piece(1_piece), false); + + p->piece_passed(1_piece); + + TEST_EQUAL(p->has_piece_passed(0_piece), true); + TEST_EQUAL(p->has_piece_passed(1_piece), true); + TEST_EQUAL(p->have_piece(1_piece), false); + + p->we_dont_have(1_piece); + + TEST_EQUAL(p->has_piece_passed(0_piece), true); + TEST_EQUAL(p->has_piece_passed(1_piece), false); + TEST_EQUAL(p->have_piece(1_piece), false); +} + +TORRENT_TEST(write_failed) +{ + auto p = setup_picker("1111111", "* * ", "1101111", "0200000"); + + TEST_EQUAL(p->has_piece_passed(0_piece), true); + TEST_EQUAL(p->has_piece_passed(1_piece), false); + TEST_EQUAL(p->have_piece(1_piece), false); + + p->piece_passed(1_piece); + + TEST_EQUAL(p->has_piece_passed(0_piece), true); + TEST_EQUAL(p->has_piece_passed(1_piece), true); + TEST_EQUAL(p->have_piece(1_piece), false); + + p->mark_as_writing({1_piece, 0}, &tmp1); + p->write_failed({1_piece, 0}); + + TEST_EQUAL(p->has_piece_passed(0_piece), true); + TEST_EQUAL(p->has_piece_passed(1_piece), false); + TEST_EQUAL(p->have_piece(1_piece), 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(1_piece); + + 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({1_piece, 0}, &tmp1); + p->lock_piece(1_piece); + + 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(1_piece); + TEST_EQUAL(stat.downloading, 0); + + p->mark_as_writing({1_piece, 0}, &tmp1); + + stat = p->piece_stats(1_piece); + TEST_EQUAL(stat.downloading, 1); + + p->write_failed({1_piece, 0}); + + stat = p->piece_stats(1_piece); + TEST_EQUAL(stat.downloading, 0); +} + +TORRENT_TEST(mark_as_canceled) +{ + auto p = setup_picker("1111111", "* * ", "1101111", ""); + + auto stat = p->piece_stats(1_piece); + TEST_EQUAL(stat.downloading, 0); + + p->mark_as_writing({1_piece, 0}, &tmp1); + + stat = p->piece_stats(1_piece); + TEST_EQUAL(stat.downloading, 1); + + p->mark_as_canceled({1_piece, 0}, &tmp1); + stat = p->piece_stats(1_piece); + 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 == 1_piece; }), 1); + TEST_EQUAL(std::count_if(downloads.begin(), downloads.end() + , [](piece_picker::downloading_piece const& p) { return p.index == 2_piece; }), 1); + TEST_EQUAL(std::count_if(downloads.begin(), downloads.end() + , [](piece_picker::downloading_piece const& p) { return p.index == 3_piece; }), 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(1_piece, 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(reprioritize_downloading) +{ + auto p = setup_picker("1111111", " ", "", ""); + bool ret; + + ret = p->mark_as_downloading({0_piece, 0}, tmp_peer); + TEST_EQUAL(ret, true); + p->mark_as_finished({0_piece, 1}, tmp_peer); + ret = p->mark_as_writing({0_piece, 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), 0_piece); + + // set the priority of the piece to 0 (while downloading it) + ret = p->set_piece_priority(0_piece, 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, 0_piece); + + // 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(0_piece, 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), 0_piece); +} + +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(0_piece, 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, 0_piece); + } + + // set the priority of the piece to 0 (while downloading it) + ret = p->set_piece_priority(0_piece, 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, 0_piece); + } + + // 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(0_piece, 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, 0_piece); + } +} + +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(0_piece, 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, 0_piece); + } + + // then mark it for downloading + ret = p->mark_as_downloading({0_piece, 0}, tmp_peer); + TEST_EQUAL(ret, true); + p->mark_as_finished({0_piece, 1}, tmp_peer); + ret = p->mark_as_writing({0_piece, 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, 0_piece); + } + + // 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(0_piece, 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), 0_piece); +} + +TORRENT_TEST(set_pad_bytes) +{ + auto p = setup_picker("1111111", " ", "4444444", ""); + p->set_pad_bytes(2_piece, 0x4000); + + bool const ret = p->mark_as_downloading({2_piece, 1}, tmp_peer); + TEST_EQUAL(ret, true); + + auto const 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, 2_piece); + + auto const blocks = p->blocks_for_piece(dl[0]); + TEST_EQUAL(blocks[0].state, piece_picker::block_info::state_none); + 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_finished); +} + +TORRENT_TEST(pad_bytes_in_piece_bytes) +{ + for (int i = 1; i < 10; ++i) + { + auto p = setup_picker("1111111", " ", "4444444", ""); + p->set_pad_bytes(2_piece, i); + TEST_EQUAL(p->pad_bytes_in_piece(0_piece), 0); + TEST_EQUAL(p->pad_bytes_in_piece(1_piece), 0); + TEST_EQUAL(p->pad_bytes_in_piece(2_piece), i); + } +} + +namespace libtorrent { +namespace { + +bool operator==(piece_count const& lhs, piece_count const& rhs) +{ + return std::tie(lhs.num_pieces, lhs.pad_bytes, lhs.last_piece) + == std::tie(rhs.num_pieces, rhs.pad_bytes, rhs.last_piece); +} + +} +} // libtorrent namespace + +TORRENT_TEST(num_pad_bytes_want) +{ + auto p = setup_picker("111", " ", "444", ""); + TEST_CHECK((p->want() == piece_count{3, 0, true})); + TEST_CHECK((p->have_want() == piece_count{0, 0, false})); + TEST_CHECK((p->have() == piece_count{0, 0, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 0, true})); + p->set_pad_bytes(2_piece, 1); + TEST_CHECK((p->want() == piece_count{3, 1, true})); + TEST_CHECK((p->have_want() == piece_count{0, 0, false})); + TEST_CHECK((p->have() == piece_count{0, 0, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 1, true})); + p->set_pad_bytes(1_piece, 2); + TEST_CHECK((p->want() == piece_count{3, 3, true})); + TEST_CHECK((p->have_want() == piece_count{0, 0, false})); + TEST_CHECK((p->have() == piece_count{0, 0, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 3, true})); + p->set_pad_bytes(0_piece, 0x4000); + TEST_CHECK((p->want() == piece_count{3, 0x4003, true})); + TEST_CHECK((p->have_want() == piece_count{0, 0, false})); + TEST_CHECK((p->have() == piece_count{0, 0, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 0x4003, true})); +} + +TORRENT_TEST(num_pad_bytes_want_filter) +{ + auto p = setup_picker("111", " ", "444", ""); + p->set_piece_priority(1_piece, dont_download); + TEST_CHECK((p->want() == piece_count{2, 0, true})); + TEST_CHECK((p->have_want() == piece_count{0, 0, false})); + TEST_CHECK((p->have() == piece_count{0, 0, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 0, true})); + p->set_pad_bytes(2_piece, 1); + TEST_CHECK((p->want() == piece_count{2, 1, true})); + TEST_CHECK((p->have_want() == piece_count{0, 0, false})); + TEST_CHECK((p->have() == piece_count{0, 0, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 1, true})); + p->set_pad_bytes(1_piece, 2); + TEST_CHECK((p->want() == piece_count{2, 1, true})); + TEST_CHECK((p->have_want() == piece_count{0, 0, false})); + TEST_CHECK((p->have() == piece_count{0, 0, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 3, true})); + p->set_pad_bytes(0_piece, 0x4000); + TEST_CHECK((p->want() == piece_count{2, 0x4001, true})); + TEST_CHECK((p->have_want() == piece_count{0, 0, false})); + TEST_CHECK((p->have() == piece_count{0, 0, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 0x4003, true})); +} + +TORRENT_TEST(num_pad_bytes_want_have) +{ + auto p = setup_picker("111", " ", "444", ""); + p->we_have(1_piece); + TEST_CHECK((p->want() == piece_count{3, 0, true})); + TEST_CHECK((p->have_want() == piece_count{1, 0, false})); + TEST_CHECK((p->have() == piece_count{1, 0, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 0, true})); + p->set_pad_bytes(2_piece, 1); + TEST_CHECK((p->want() == piece_count{3, 1, true})); + TEST_CHECK((p->have_want() == piece_count{1, 0, false})); + TEST_CHECK((p->have() == piece_count{1, 0, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 1, true})); + p->set_pad_bytes(1_piece, 2); + TEST_CHECK((p->want() == piece_count{3, 3, true})); + TEST_CHECK((p->have_want() == piece_count{1, 2, false})); + TEST_CHECK((p->have() == piece_count{1, 2, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 3, true})); + p->set_pad_bytes(0_piece, 0x4000); + TEST_CHECK((p->want() == piece_count{3, 0x4003, true})); + TEST_CHECK((p->have_want() == piece_count{1, 2, false})); + TEST_CHECK((p->have() == piece_count{1, 2, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 0x4003, true})); +} + +TORRENT_TEST(num_pad_bytes_we_have) +{ + auto p = setup_picker("111", " ", "444", ""); + p->set_pad_bytes(2_piece, 1); + p->set_pad_bytes(1_piece, 2); + p->set_pad_bytes(0_piece, 0x4000); + + p->we_have(1_piece); + TEST_CHECK((p->want() == piece_count{3, 0x4003, true})); + TEST_CHECK((p->have_want() == piece_count{1, 2, false})); + TEST_CHECK((p->have() == piece_count{1, 2, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 0x4003, true})); +} + +TORRENT_TEST(num_pad_bytes_dont_want_have) +{ + auto p = setup_picker("111", " ", "444", ""); + p->set_pad_bytes(2_piece, 1); + p->set_pad_bytes(1_piece, 2); + p->set_pad_bytes(0_piece, 0x4000); + + p->set_piece_priority(1_piece, dont_download); + p->we_have(1_piece); + TEST_CHECK((p->want() == piece_count{2, 0x4001, true})); + TEST_CHECK((p->have_want() == piece_count{0, 0, false})); + TEST_CHECK((p->have() == piece_count{1, 2, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 0x4003, true})); +} + +TORRENT_TEST(num_pad_bytes_have_dont_want) +{ + auto p = setup_picker("111", " ", "444", ""); + p->set_pad_bytes(2_piece, 1); + p->set_pad_bytes(1_piece, 2); + p->set_pad_bytes(0_piece, 0x4000); + + p->we_have(1_piece); + p->set_piece_priority(1_piece, dont_download); + TEST_CHECK((p->want() == piece_count{2, 0x4001, true})); + TEST_CHECK((p->have_want() == piece_count{0, 0, false})); + TEST_CHECK((p->have() == piece_count{1, 2, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 0x4003, true})); +} + +TORRENT_TEST(have_dont_want_pad_bytes) +{ + auto p = setup_picker("111", " ", "444", ""); + p->we_have(1_piece); + p->set_piece_priority(1_piece, dont_download); + p->set_pad_bytes(2_piece, 1); + p->set_pad_bytes(1_piece, 2); + p->set_pad_bytes(0_piece, 0x4000); + + TEST_CHECK((p->want() == piece_count{2, 0x4001, true})); + TEST_CHECK((p->have_want() == piece_count{0, 0, false})); + TEST_CHECK((p->have() == piece_count{1, 2, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 0x4003, true})); +} + +TORRENT_TEST(pad_bytes_have) +{ + { + auto p = setup_picker("1111111", " ", "4444444", ""); + p->set_pad_bytes(2_piece, 10); + TEST_CHECK(!p->have_piece(0_piece)); + TEST_CHECK(!p->have_piece(1_piece)); + TEST_CHECK(!p->have_piece(2_piece)); + TEST_CHECK(!p->have_piece(3_piece)); + } + + { + auto p = setup_picker("1111111", " ", "4444444", ""); + p->set_pad_bytes(2_piece, default_block_size); + TEST_CHECK(!p->have_piece(0_piece)); + TEST_CHECK(!p->have_piece(1_piece)); + TEST_CHECK(!p->have_piece(2_piece)); + TEST_CHECK(!p->have_piece(3_piece)); + } + + { + auto p = setup_picker("1111111", " ", "4444444", ""); + p->set_pad_bytes(2_piece, blocks_per_piece * default_block_size); + TEST_CHECK(!p->have_piece(0_piece)); + TEST_CHECK(!p->have_piece(1_piece)); + TEST_CHECK(p->have_piece(2_piece)); + TEST_CHECK(!p->have_piece(3_piece)); + } + + { + auto p = setup_picker("1111111", " ", "4444444", ""); + p->set_pad_bytes(2_piece, blocks_per_piece * default_block_size); + p->set_pad_bytes(1_piece, default_block_size); + TEST_CHECK(!p->have_piece(0_piece)); + TEST_CHECK(!p->have_piece(1_piece)); + TEST_CHECK(p->have_piece(2_piece)); + TEST_CHECK(!p->have_piece(3_piece)); + } +} + +TORRENT_TEST(invalid_piece_size) +{ + int const num_pieces = 100; + // one byte is enough to require one more block + { + int const piece_size =default_block_size * piece_picker::max_blocks_per_piece + 1; + TEST_THROW(piece_picker(std::int64_t(num_pieces) * piece_size, piece_size)); + } + + // a full block will (obviously) also exceed the limit + { + int const piece_size =default_block_size * (piece_picker::max_blocks_per_piece + 1); + TEST_THROW(piece_picker(std::int64_t(num_pieces) * piece_size, piece_size)); + } + + // exactly the limit should be no problem + { + int const piece_size =default_block_size * piece_picker::max_blocks_per_piece; + piece_picker p(std::int64_t(num_pieces) * piece_size, piece_size); + } +} + +TORRENT_TEST(mark_as_pad_pick_more_than_one_block) +{ + auto p = setup_picker("111", " ", "444", ""); + p->set_pad_bytes(2_piece, 0x4100); + + std::vector picked = pick_pieces(p, " *", 4, 0, nullptr + , options, empty_vector); + + TEST_CHECK(verify_pick(p, picked)); + TEST_EQUAL(picked.size(), 3); + TEST_CHECK((picked[0] == piece_block{2_piece, 0})); + TEST_CHECK((picked[1] == piece_block{2_piece, 1})); + TEST_CHECK((picked[2] == piece_block{2_piece, 2})); + // notably, block (2,2) should not be picked +} + +TORRENT_TEST(mark_as_pad_pick_full_piece) +{ + auto p = setup_picker("111", " ", "444", ""); + p->set_pad_bytes(2_piece, default_piece_size); + + std::vector picked = pick_pieces(p, " **", 8, 0, nullptr + , options, empty_vector); + + TEST_CHECK(verify_pick(p, picked)); + TEST_EQUAL(picked.size(), 4); + TEST_CHECK((picked[0] == piece_block{1_piece, 0})); + TEST_CHECK((picked[1] == piece_block{1_piece, 1})); + TEST_CHECK((picked[2] == piece_block{1_piece, 2})); + TEST_CHECK((picked[3] == piece_block{1_piece, 3})); + // notably, nothing is picked from piece 2 +} + +TORRENT_TEST(mark_as_pad_pick_less_than_one_block) +{ + auto p = setup_picker("111", " ", "444", ""); + p->set_pad_bytes(2_piece, 0x100); + + std::vector picked = pick_pieces(p, " *", 4, 0, nullptr + , options, empty_vector); + + TEST_CHECK(verify_pick(p, picked)); + TEST_EQUAL(picked.size(), 4); + TEST_CHECK((picked[0] == piece_block{2_piece, 0})); + TEST_CHECK((picked[1] == piece_block{2_piece, 1})); + TEST_CHECK((picked[2] == piece_block{2_piece, 2})); + TEST_CHECK((picked[3] == piece_block{2_piece, 3})); + // notably, block (2,2) *is* picked +} + +TORRENT_TEST(mark_as_pad_pick_exactly_one_block) +{ + auto p = setup_picker("111", " ", "444", ""); + p->set_pad_bytes(2_piece, 0x4000); + + std::vector picked = pick_pieces(p, " *", 4, 0, nullptr + , options, empty_vector); + + TEST_CHECK(verify_pick(p, picked)); + TEST_EQUAL(picked.size(), 3); + TEST_CHECK((picked[0] == piece_block{2_piece, 0})); + TEST_CHECK((picked[1] == piece_block{2_piece, 1})); + TEST_CHECK((picked[2] == piece_block{2_piece, 2})); + // notably, block (2,2) should not be picked +} + +TORRENT_TEST(mark_as_pad_pick_short_last_piece) +{ + auto p = std::make_shared( + 3 * default_piece_size - default_block_size, default_piece_size); + p->inc_refcount(0_piece, &tmp0); + p->inc_refcount(1_piece, &tmp0); + p->inc_refcount(2_piece, &tmp0); + + p->set_pad_bytes(2_piece, 0x400); + + std::vector picked = pick_pieces(p, " *", 4, 0, nullptr + , options, empty_vector); + + TEST_CHECK(verify_pick(p, picked)); + TEST_EQUAL(picked.size(), 3); + TEST_CHECK((picked[0] == piece_block{2_piece, 0})); + TEST_CHECK((picked[1] == piece_block{2_piece, 1})); + TEST_CHECK((picked[2] == piece_block{2_piece, 2})); + // there is no block 3 in this piece +} + +TORRENT_TEST(mark_as_pad_pick_short_last_piece_prefer_contiguos) +{ + auto p = std::make_shared( + 3 * default_piece_size - default_block_size, default_piece_size); + p->inc_refcount(0_piece, &tmp0); + p->inc_refcount(1_piece, &tmp0); + p->inc_refcount(2_piece, &tmp0); + + p->set_pad_bytes(2_piece, 0x400); + + std::vector picked = pick_pieces(p, "***", 12, 12, nullptr + , options, empty_vector); + + TEST_CHECK(verify_pick(p, picked)); + TEST_EQUAL(picked.size(), 11); + std::sort(picked.begin(), picked.end()); + TEST_CHECK((picked[0] == piece_block{0_piece, 0})); + TEST_CHECK((picked[1] == piece_block{0_piece, 1})); + TEST_CHECK((picked[2] == piece_block{0_piece, 2})); + TEST_CHECK((picked[3] == piece_block{0_piece, 3})); + TEST_CHECK((picked[4] == piece_block{1_piece, 0})); + TEST_CHECK((picked[5] == piece_block{1_piece, 1})); + TEST_CHECK((picked[6] == piece_block{1_piece, 2})); + TEST_CHECK((picked[7] == piece_block{1_piece, 3})); + TEST_CHECK((picked[8] == piece_block{2_piece, 0})); + TEST_CHECK((picked[9] == piece_block{2_piece, 1})); + TEST_CHECK((picked[10] == piece_block{2_piece, 2})); + // there is no block 3 in this piece +} + +//#error test with odd block sizes +TORRENT_TEST(pad_blocks_some_wanted_odd_blocks) +{ + int const piece_size = default_block_size / 3; + auto p = std::make_shared( + 3 * piece_size, piece_size); + + p->we_have(1_piece); + p->set_piece_priority(1_piece, dont_download); + p->set_pad_bytes(2_piece, 1); + p->set_pad_bytes(1_piece, 2); + p->set_pad_bytes(0_piece, 0x1400); + + TEST_CHECK((p->want() == piece_count{2, 0x1401, true})); + TEST_CHECK((p->have_want() == piece_count{0, 0, false})); + TEST_CHECK((p->have() == piece_count{1, 2, false})); + TEST_CHECK((p->all_pieces() == piece_count{3, 0x1403, true})); +} + +TORRENT_TEST(mark_as_pad_downloading) +{ + auto p = setup_picker("1111111", " ", "4444444", ""); + p->set_pad_bytes(2_piece, 0x4000); + + bool const ret = p->mark_as_downloading({2_piece, 3}, tmp_peer); + TEST_EQUAL(ret, false); + + auto const 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, 2_piece); + + auto const blocks = p->blocks_for_piece(dl[0]); + TEST_EQUAL(blocks[0].state, piece_picker::block_info::state_none); + 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_finished); +} + +TORRENT_TEST(mark_as_pad_seeding) +{ + auto p = setup_picker("1", " ", "4", ""); + p->set_pad_bytes(0_piece, 0x4000 * 3); + + TEST_CHECK(!p->is_seeding()); + + p->mark_as_finished({0_piece, 0}, tmp_peer); + + TEST_CHECK(!p->is_seeding()); + p->piece_passed(0_piece); + TEST_CHECK(p->is_seeding()); +} + +TORRENT_TEST(mark_as_pad_whole_piece_seeding) +{ + auto p = setup_picker("11", " ", "44", ""); + p->set_pad_bytes(0_piece, 0x4000 * 4); + TEST_CHECK(p->have_piece(0_piece)); + + TEST_CHECK(!p->is_seeding()); + + p->mark_as_finished({1_piece, 0}, nullptr); + p->mark_as_finished({1_piece, 1}, nullptr); + p->mark_as_finished({1_piece, 2}, nullptr); + p->mark_as_finished({1_piece, 3}, nullptr); + + TEST_CHECK(!p->is_seeding()); + p->piece_passed(1_piece); + TEST_CHECK(p->is_seeding()); +} + +TORRENT_TEST(pad_bytes_in_piece) +{ + auto p = setup_picker("11", " ", "44", ""); + p->set_pad_bytes(0_piece, 0x4000 * 3); + + TEST_EQUAL(p->pad_bytes_in_piece(0_piece), 0x4000 * 3); + TEST_EQUAL(p->pad_bytes_in_piece(1_piece), 0); +} + +TORRENT_TEST(pad_bytes_in_last_piece) +{ + auto p = setup_picker("11", " ", "44", ""); + p->set_pad_bytes(1_piece, 0x4000 * 3); + + TEST_EQUAL(p->pad_bytes_in_piece(1_piece), 0x4000 * 3); + TEST_EQUAL(p->pad_bytes_in_piece(0_piece), 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_bytes > 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_bytes, 3 * 0x4000); +} + +void validate_no_pieces(piece_count const& c) +{ + TEST_EQUAL(c.last_piece, false); + TEST_EQUAL(c.num_pieces, 0); + TEST_EQUAL(c.pad_bytes, 0); +} +} + +TORRENT_TEST(pad_blocks_all_filtered) +{ + auto p = setup_picker("1111", " ", "0000", ""); + p->set_pad_bytes(1_piece, 0x4000 * 2); + p->set_pad_bytes(2_piece, 0x4000); + + 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->set_pad_bytes(1_piece, 0x4000 * 2); + p->set_pad_bytes(2_piece, 0x4000); + + 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->set_pad_bytes(1_piece, 0x8000); + p->set_pad_bytes(2_piece, 0x4000); + + 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_bytes, 2 * 0x4000); +} + +TORRENT_TEST(started_hash_job) +{ + auto p = setup_picker("1111", " ", "0404", ""); + TEST_CHECK(!p->is_hashing(0_piece)); + TEST_CHECK(!p->is_hashing(1_piece)); + TEST_CHECK(!p->is_hashing(2_piece)); + TEST_CHECK(!p->is_hashing(3_piece)); + + // we cannot start a hash job unless the block is also marked as downloading, + // writing or finished + p->mark_as_downloading(piece_block(0_piece, 0), tmp_peer); + + TEST_CHECK(!p->is_hashing(0_piece)); + TEST_CHECK(!p->is_hashing(1_piece)); + TEST_CHECK(!p->is_hashing(2_piece)); + TEST_CHECK(!p->is_hashing(3_piece)); + + p->started_hash_job(0_piece); + TEST_CHECK(p->is_hashing(0_piece)); + TEST_CHECK(!p->is_hashing(1_piece)); + TEST_CHECK(!p->is_hashing(2_piece)); + TEST_CHECK(!p->is_hashing(3_piece)); + + p->completed_hash_job(0_piece); + TEST_CHECK(!p->is_hashing(0_piece)); + TEST_CHECK(!p->is_hashing(1_piece)); + TEST_CHECK(!p->is_hashing(2_piece)); + TEST_CHECK(!p->is_hashing(3_piece)); +} + +namespace { + +std::vector full_piece(piece_index_t const p, int const blocks) +{ + std::vector ret; + for (int i = 0;i < blocks; ++i) + ret.push_back(piece_block(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 * default_block_size); + + 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_piece, blocks)); + mark_downloading(p, full_piece(2_piece, 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_piece, blocks)); + mark_downloading(p, full_piece(5_piece, 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_piece, blocks)); + mark_downloading(p, full_piece(0_piece, 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_piece, blocks)); + mark_downloading(p, full_piece(1_piece, 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_piece, blocks)); + mark_downloading(p, full_piece(3_piece, 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 * default_block_size); + // 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_piece, 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_piece, 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 * default_block_size); + // 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_piece, 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_piece, 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 * default_block_size); + // open up the first 5 extents + mark_downloading(p, full_piece(0_piece, blocks), &tmp0, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(2_piece, blocks), &tmp1, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(4_piece, blocks), &tmp2, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(6_piece, blocks), &tmp3, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(8_piece, 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_piece, 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_piece, 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 * default_block_size); + // open up the first 5 extents + mark_downloading(p, full_piece(0_piece, blocks), &tmp0, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(2_piece, blocks), &tmp1, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(4_piece, blocks), &tmp2, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(6_piece, blocks), &tmp3, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(8_piece, 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(0_piece); + p->we_have(1_piece); + + // 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_piece, 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_piece, 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 * default_block_size); + // download 5 pieces from the first extent + mark_downloading(p, full_piece(0_piece, blocks), &tmp0, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(2_piece, blocks), &tmp1, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(4_piece, blocks), &tmp2, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(6_piece, blocks), &tmp3, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(1_piece, 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_piece, 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_piece, blocks)); +} + +TORRENT_TEST(piece_block_exported) +{ + // piece_block is part of the public API via picker_log_alert::blocks + // ensure it's exported by using piece_block::invalid + TEST_EQUAL(piece_block::invalid.piece_index, std::numeric_limits::max()); + TEST_EQUAL(piece_block::invalid.block_index, std::numeric_limits::max()); +} + +//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..533f5d2 --- /dev/null +++ b/test/test_primitives.cpp @@ -0,0 +1,256 @@ +/* + +Copyright (c) 2007-2009, 2012-2020, Arvid Norberg +Copyright (c) 2018-2019, Steven Siloti +Copyright (c) 2018, 2020, 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/entry.hpp" +#include "libtorrent/socket_io.hpp" // for print_endpoint +#include "libtorrent/aux_/announce_entry.hpp" +#include "libtorrent/aux_/byteswap.hpp" +#include "libtorrent/hex.hpp" // from_hex +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/client_data.hpp" + +#include "test.hpp" +#include "setup_transfer.hpp" // for supports_ipv6 + +using namespace lt; + +TORRENT_TEST(retry_interval) +{ + // make sure the retry interval keeps growing + // on failing announces + aux::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().info_hashes[protocol_version::V1].failed(tracker_backoff, seconds32(5)); + int const delay = static_cast(total_seconds(ae.endpoints.front().info_hashes[protocol_version::V1].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::ssrf_mitigation).message() == "blocked by SSRF mitigation"); + TEST_CHECK(error_code(errors::blocked_by_idna).message() == "blocked by IDNA ban"); + + TEST_CHECK(error_code(errors::torrent_inconsistent_hashes).message() == "v1 and v2 hashes do not describe the same data"); + + 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(make_address("::1", ec), 0)); + peers.insert(std::make_pair(make_address("::2", ec), 3)); + peers.insert(std::make_pair(make_address("::3", ec), 5)); + i = peers.find(make_address("::2", ec)); + TEST_CHECK(i != peers.end()); + if (i != peers.end()) + { + TEST_CHECK(i->first == make_address("::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); +} + +TORRENT_TEST(client_data_assign) +{ + client_data_t v; + TEST_CHECK(v.get() == nullptr); + int a = 1337; + v = &a; + TEST_CHECK(v.get() == &a); + TEST_CHECK(*v.get() == 1337); + TEST_CHECK(v.get() == nullptr); + TEST_CHECK(v.get() == nullptr); + TEST_CHECK(v.get() == nullptr); + TEST_CHECK(v.get() == nullptr); + + TEST_CHECK(static_cast(v) == &a); + TEST_CHECK(*static_cast(v) == 1337); + TEST_CHECK(static_cast(v) == nullptr); + TEST_CHECK(static_cast(v) == nullptr); + TEST_CHECK(static_cast(v) == nullptr); + TEST_CHECK(static_cast(v) == nullptr); + + float b = 42.f; + v = &b; + TEST_CHECK(v.get() == &b); + TEST_CHECK(*v.get() == 42.f); + TEST_CHECK(v.get() == nullptr); + TEST_CHECK(v.get() == nullptr); + TEST_CHECK(v.get() == nullptr); + TEST_CHECK(v.get() == nullptr); + + TEST_CHECK(static_cast(v) == &b); + TEST_CHECK(*static_cast(v) == 42.f); + TEST_CHECK(static_cast(v) == nullptr); + TEST_CHECK(static_cast(v) == nullptr); + TEST_CHECK(static_cast(v) == nullptr); + TEST_CHECK(static_cast(v) == nullptr); +} + +TORRENT_TEST(client_data_initialize) +{ + int a = 1337; + client_data_t v(&a); + TEST_CHECK(v.get() == &a); + TEST_CHECK(*v.get() == 1337); +} + +TORRENT_TEST(announce_endpoint_initialize) +{ + // announce_endpoint has an array of announce_infohash and + // announce_infohash has the constructor marked TORRENT_UNEXPORT + // it's important that announce_endpoint provides a constructor + announce_endpoint ae; + TEST_EQUAL(ae.enabled, true); +} + +TORRENT_TEST(byteswap) +{ + TEST_CHECK(aux::swap_byteorder(0x12345678) == 0x78563412); + TEST_CHECK(aux::swap_byteorder(0xfeefaffa) == 0xfaafeffe); +} + diff --git a/test/test_priority.cpp b/test/test_priority.cpp new file mode 100644 index 0000000..e975de8 --- /dev/null +++ b/test/test_priority.cpp @@ -0,0 +1,713 @@ +/* + +Copyright (c) 2018, Steven Siloti +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/session_params.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 + +#include "test.hpp" +#include "test_utils.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" +#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, test_listen_interface()); +#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, test_listen_interface()); + lt::session ses2(pack); + + torrent_handle tor1; + torrent_handle tor2; + + error_code ec; + create_directory("tmp1_priority", ec); + 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_hashes.v1 = 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_hashes.v1 = sha1_hash("abababababababababab"); + addp.save_path = "."; + torrent_handle h = ses.add_torrent(addp); + + h.file_priority(0_file, 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(0_file), 0_pri); + h.file_priority(0_file, 1_pri); + std::this_thread::sleep_for(lt::milliseconds(100)); + TEST_EQUAL(h.file_priority(0_file), 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_hashes.v1 = 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(2_piece, 0_pri); + TEST_EQUAL(h.piece_priority(2_piece), 4_pri); + h.piece_priority(2_piece, 1_pri); + TEST_EQUAL(h.piece_priority(2_piece), 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); + 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 < 40; ++i) + { + print_alerts(ses, "ses", true, true, &on_alert); + 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 < 20; ++i) + { + print_alerts(ses, "ses", true, true, &on_alert); + 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(0_piece), prio); + + using prio_vec = std::vector>; + h.prioritize_pieces(prio_vec{{0_piece, new_prio}}); + TEST_EQUAL(h.piece_priority(0_piece), 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(0_piece), new_prio); + } +} + +namespace { +template +lt::file_index_t rand_file(Engine& rng, int num_files) +{ + auto i = std::uniform_int_distribution(0, num_files - 1)(rng); + return file_index_t(i); +} + +template +lt::download_priority_t rand_prio(Engine& rng) +{ + auto i = std::uniform_int_distribution(0, 7)(rng); + return lt::download_priority_t(static_cast(i)); +} +} + +TORRENT_TEST(file_priority_stress_test) +{ + add_torrent_params atp; + auto ti = generate_torrent(); + int const num_files = ti->num_files(); + + lt::aux::vector + local_prios(static_cast(num_files), lt::default_priority); + + atp.save_path = "."; + atp.ti = ti; + atp.file_priorities = local_prios; + atp.flags &= ~torrent_flags::need_save_resume; + + lt::session ses(settings()); + torrent_handle h = ses.add_torrent(atp); + TEST_CHECK(h.status().need_save_resume == false); + + std::mt19937 rng(0x82daf973); + bool first = true; + for (int i = 0; i < 1000; ++i) + { + auto const f = rand_file(rng, num_files); + auto const p = rand_prio(rng); + h.file_priority(f, p); + local_prios[f] = p; + + // updating the file priorities is asynchronous, it shouldn't have taken + // effect quite yet + if (first) + TEST_CHECK(h.status().need_save_resume == false); + first = false; + } + + auto tp = h.get_file_priorities(); + for (int i = 0; i < 10 && tp != local_prios; ++i) + { + std::this_thread::sleep_for(lt::milliseconds(500)); + tp = h.get_file_priorities(); + } + + std::cout << "torrent file prios:\n"; + for (auto const p : tp) + std::cout << " " << p; + std::cout << '\n'; + + std::cout << "expected file prios:\n"; + for (auto const p : local_prios) + std::cout << " " << p; + std::cout << '\n'; + + TEST_CHECK(tp == local_prios); + TEST_CHECK(h.status().need_save_resume == true); + + auto const pp = h.get_piece_priorities(); + auto const& fs = ti->files(); + lt::piece_index_t i(0); + + std::cout << "piece prios:\n"; + for (auto p : pp) + { + auto const files = fs.map_block(i, 0, fs.piece_length()); + auto expected_prio = lt::dont_download; + for (auto const& f: files) + expected_prio = std::max(local_prios[f.file_index], expected_prio); + + std::cout << " " << p; + TEST_EQUAL(p, expected_prio); + ++i; + } + std::cout << '\n'; +} diff --git a/test/test_privacy.cpp b/test/test_privacy.cpp new file mode 100644 index 0000000..38123f3 --- /dev/null +++ b/test/test_privacy.cpp @@ -0,0 +1,346 @@ +/* + +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2018, 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 "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 "libtorrent/session_params.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); + + auto s = std::make_unique(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({make_address_v4("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) ? 2 : 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().c_str()); + 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..21a492f --- /dev/null +++ b/test/test_read_piece.cpp @@ -0,0 +1,161 @@ +/* + +Copyright (c) 2013, 2015-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, 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/session.hpp" +#include "libtorrent/session_params.hpp" +#include "test.hpp" +#include "test_utils.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; + 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); + + // 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_hashes().v1).c_str()); + + settings_pack sett = settings(); + sett.set_str(settings_pack::listen_interfaces, test_listen_interface()); + 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(1_piece, 0, torrent_handle::alert_when_available); + } + else + { + tor1.read_piece(1_piece); + } + + 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, 1_piece); + } + } + + 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..bd0a691 --- /dev/null +++ b/test/test_read_resume.cpp @@ -0,0 +1,398 @@ +/* + +Copyright (c) 2016-2020, Arvid Norberg +Copyright (c) 2016, 2018, Alden Torres +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 "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["last_seen_complete"] = 1353; + + 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_hashes.v1, 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.last_seen_complete, 1353); + + 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_info_hash2) +{ + entry rd; + + rd["file-format"] = "libtorrent resume file"; + rd["file-version"] = 1; + // it's OK to *only* have a v2 hash + rd["info-hash2"] = "01234567890123456789012345678901"; + + 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()); +} + +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); + + 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_hashes().v1.to_string(); + rd["info"] = bdecode(ti->info_section()); + + 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_hashes(), ti->info_hashes()); + 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_last_seen_complete) +{ + add_torrent_params atp; + atp.last_seen_complete = 42; + 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{{42_piece, 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_name) +{ + add_torrent_params atp; + atp.name = "foobar"; + 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); + } +} + +TORRENT_TEST(round_trip_info_hash) +{ + add_torrent_params atp; + atp.info_hashes.v2 = sha256_hash{"21212121212121212121212121212121"}; + test_roundtrip(atp); + entry e = write_resume_data(atp); + TEST_CHECK(e["info-hash2"] == "21212121212121212121212121212121"); +} + +TORRENT_TEST(round_trip_merkle_trees) +{ + add_torrent_params atp; + atp.merkle_trees = aux::vector, file_index_t>{ + {sha256_hash{"01010101010101010101010101010101"}, sha256_hash{"21212121212121212121212121212121"}} + , {sha256_hash{"23232323232323232323232323232323"}, sha256_hash{"43434343434343434343434343434343"}} + }; + test_roundtrip(atp); +} + +TORRENT_TEST(round_trip_merkle_tree_mask) +{ + add_torrent_params atp; + atp.merkle_trees = aux::vector, file_index_t>{ + {sha256_hash{"01010101010101010101010101010101"}, sha256_hash{"21212121212121212121212121212121"}} + , {sha256_hash{"23232323232323232323232323232323"}, sha256_hash{"43434343434343434343434343434343"}} + }; + atp.merkle_tree_mask = aux::vector, file_index_t>{{false, false, false, true, true, true, true}}; + test_roundtrip(atp); +} + +TORRENT_TEST(round_trip_verified_leaf_hashes) +{ + add_torrent_params atp; + atp.verified_leaf_hashes = aux::vector, file_index_t>{ + {true, true, false, false}, {false, true, false, true}}; + test_roundtrip(atp); +} diff --git a/test/test_receive_buffer.cpp b/test/test_receive_buffer.cpp new file mode 100644 index 0000000..5ff588f --- /dev/null +++ b/test/test_receive_buffer.cpp @@ -0,0 +1,258 @@ +/* + +Copyright (c) 2016-2019, Arvid Norberg +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 "test.hpp" +#include "libtorrent/aux_/receive_buffer.hpp" + +using namespace lt; +using lt::aux::receive_buffer; + +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..feac2b7 --- /dev/null +++ b/test/test_recheck.cpp @@ -0,0 +1,121 @@ +/* + +Copyright (c) 2014-2019, Arvid Norberg +Copyright (c) 2016, 2018, 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/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/session_params.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 "test_utils.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, test_listen_interface()); + 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..6049db3 --- /dev/null +++ b/test/test_remap_files.cpp @@ -0,0 +1,225 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2015, Jakob Petsovits +Copyright (c) 2016, Eugene Shalygin +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017-2018, 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/session.hpp" +#include "libtorrent/session_params.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 "test_utils.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{ {0x8000 * 2, 0x8000} }; + int const piece_size = 0x8000; + auto t = make_torrent(file_sizes, piece_size); + + static std::array const remap_file_sizes + {{0x8000, 0x8000 * 2}}; + + file_storage fs = make_file_storage(remap_file_sizes, piece_size, "multifile-"); + + t->remap_files(fs); + + auto const alert_mask = alert_category::all +#if TORRENT_ABI_VERSION <= 2 + & ~alert_category::stats +#endif + ; + + 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 (auto i = 0_file; 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..9c1c545 --- /dev/null +++ b/test/test_remove_torrent.cpp @@ -0,0 +1,224 @@ +/* + +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2018, d-komarov +Copyright (c) 2018, 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/session.hpp" +#include "libtorrent/session_params.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 "test_utils.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(); + + pack.set_str(settings_pack::listen_interfaces, test_listen_interface()); + lt::session ses1(pack); + + pack.set_str(settings_pack::listen_interfaces, test_listen_interface()); + 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" + , 8 * 1024, num_pieces, false, create_torrent::v1_only); + 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, true, false, nullptr + , create_torrent::v1_only); + + 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(); + std::cout << "st1.total_payload_upload: " << st1.total_payload_upload << '\n'; + std::cout << "st2.num_pieces: " << st2.num_pieces << '\n'; + st2 = tor2.status(); + + if (test == mid_download && st2.num_pieces > 1) + { + 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 4 seconds, we're failing the test + if (st1.total_payload_upload == 0 && i > 40) + { + 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_files_and_partfile) +{ + test_remove_torrent(session::delete_files | session::delete_partfile); +} + +TORRENT_TEST(remove_torrent_and_just_partfile) +{ + test_remove_torrent(session::delete_partfile); +} + +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..891b5b4 --- /dev/null +++ b/test/test_resolve_links.cpp @@ -0,0 +1,235 @@ +/* + +Copyright (c) 2018, Steven Siloti +Copyright (c) 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016, 2018, 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 "test_utils.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 "libtorrent/session.hpp" + +#include "make_torrent.hpp" +#include "setup_transfer.hpp" // for wait_for_seeding + +#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::v1_only); + lt::create_torrent t2(fs2, 1024, lt::create_torrent::v1_only); + + t1.set_hash(0_piece, sha1_hash::max()); + t1.set_hash(1_piece, sha1_hash::max()); + t2.set_hash(0_piece, sha1_hash::max()); + t2.set_hash(1_piece, sha1_hash("01234567890123456789")); + + 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); +} + +TORRENT_TEST(pick_up_existing_file) +{ + lt::settings_pack pack; + pack.set_int(settings_pack::alert_mask, 0xffffff); + lt::session ses(pack); + + auto a = torrent_args() + .file("34092,name=cruft-1") + .file("31444,padfile") + .file("9000000,name=dupliicate-file") + .file("437184,padfile") + .file("1348,name=cruft-2") + .name("test-1") + .collection("test-collection"); + auto seeding_torrent = make_test_torrent(a); + generate_files(*seeding_torrent, "."); + + lt::add_torrent_params atp; + atp.ti = seeding_torrent; + atp.save_path = "."; + ses.add_torrent(atp); + + wait_for_seeding(ses, "add-seed"); + + auto b = torrent_args() + .file("52346,name=cruft-3") + .file("13190,padfile") + .file("9000000,name=dupliicate-file-with-different-name") + .file("437184,padfile") + .file("40346,name=cruft-4") + .name("test-2") + .collection("test-collection"); + auto downloading_torrent = make_test_torrent(b); + + atp.ti = downloading_torrent; + auto handle = ses.add_torrent(atp); + + wait_for_downloading(ses, "add-downloader"); + + std::vector file_progress; + handle.file_progress(file_progress); + TEST_CHECK(file_progress[2] == 9000000); +} + +#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..f0abff3 --- /dev/null +++ b/test/test_resume.cpp @@ -0,0 +1,1842 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2020, AllSeeingEyeTolledEweSew +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_params.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 "libtorrent/alert_types.hpp" +#include "setup_transfer.hpp" +#include "test_utils.hpp" + +#include "test.hpp" +#include "test_utils.hpp" +#include "settings.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" + +#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_hashes().v1.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; + rd["last_seen_complete"] = 1353; + 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) +{ + bool const with_files = (flags & torrent_flags::seed_mode) && !(flags & torrent_flags::no_verify_files); + std::shared_ptr ti = generate_torrent(with_files); + + 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; + + if (with_files) + { + p.save_path = "."; + } + else + { +#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_hashes, ti->info_hashes()); + return h; +} + +void default_tests(torrent_status const& s, lt::time_point const time_now) +{ + TORRENT_UNUSED(time_now); + // 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(time_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); + + TEST_CHECK(s.finished_time < 1352 + 2); + TEST_CHECK(s.finished_time >= 1352); +#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); + + TEST_EQUAL(s.last_seen_complete, 1353); +} + +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(0_piece, 0_pri); + h.piece_priority(ti->last_piece(), 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(0_piece), 0_pri); + TEST_EQUAL(h.piece_priority(1_piece), 4_pri); + TEST_EQUAL(h.piece_priority(ti->last_piece()), 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_EQUAL(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); + ofstream("add_torrent_params_test" SEP "test_resume" SEP "tmp1").write(a.data(), std::streamsize(a.size())); + ofstream("add_torrent_params_test" SEP "test_resume" SEP "tmp2").write(b.data(), std::streamsize(b.size())); + 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(0_piece); + p.have_pieces.set_bit(1_piece); + + 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_hashes, ti->info_hashes()); + TEST_EQUAL(s.pieces.size(), ti->num_pieces()); + TEST_CHECK(s.pieces.size() >= 4); + TEST_EQUAL(s.pieces[0_piece], true); + TEST_EQUAL(s.pieces[1_piece], true); + TEST_EQUAL(s.pieces[2_piece], false); + TEST_EQUAL(s.pieces[3_piece], 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[0_piece], true); + TEST_EQUAL(pieces[1_piece], true); + TEST_EQUAL(pieces[2_piece], false); + TEST_EQUAL(pieces[3_piece], 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); + ofstream("add_torrent_params_test" SEP "test_resume" SEP "tmp1").write(a.data(), std::streamsize(a.size())); + ofstream("add_torrent_params_test" SEP "test_resume" SEP "tmp2").write(b.data(), std::streamsize(b.size())); + 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_hashes, ti->info_hashes()); + 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()); + + auto const now = lt::clock_type::now(); + torrent_status s = test_resume_flags(ses, {}, "", "", true).status(); + default_tests(s, now); +#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()); + auto const now = lt::clock_type::now(); + torrent_status s = test_resume_flags(ses + , torrent_flags::use_resume_save_path, "", "", true).status(); + default_tests(s, now); +#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()); + auto const now = lt::clock_type::now(); + torrent_status s = test_resume_flags(ses + , torrent_flags::override_resume_data + | torrent_flags::paused, "", "", true).status(); + + default_tests(s, now); +#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()); + auto const now = lt::clock_type::now(); + torrent_status s = test_resume_flags(ses, torrent_flags::override_resume_data + | torrent_flags::seed_mode, "", "", true).status(); + default_tests(s, now); + 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()); + auto const now = lt::clock_type::now(); + torrent_status s = test_resume_flags(ses + , torrent_flags::upload_mode, "", "", true).status(); + default_tests(s, now); +#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()); + auto const now = lt::clock_type::now(); + torrent_status s = test_resume_flags(ses + , torrent_flags::override_resume_data + | torrent_flags::share_mode, "", "", true).status(); + default_tests(s, now); +#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 + auto const now = lt::clock_type::now(); + torrent_status s = test_resume_flags(ses + , torrent_flags::auto_managed, "", "", true).status(); + default_tests(s, now); +#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 + auto const now = lt::clock_type::now(); + torrent_status s = test_resume_flags(ses, torrent_flags::paused, "", "", true).status(); + default_tests(s, now); +#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_hashes().v1.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)); + + if (mix_prios) + rd["pieces"] = std::string(std::size_t(ti->num_pieces()), '\x01'); + else + rd["pieces"] = std::string(std::size_t(ti->num_pieces()), '\x00'); + + // 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); + + lt::torrent_status s = h.status(); + for (int i = 0; i < 40; ++i) + { + if (s.state != torrent_status::checking_files + && s.state != torrent_status::checking_resume_data) + break; + std::this_thread::sleep_for(lt::milliseconds(100)); + s = h.status(); + } + + // Once a torrent becomes seed, any piece- and file priorities are + // forgotten and all bytes are considered "wanted". So, whether we "want" + // all bytes here or not, depends on whether we're a seed + 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_hashes().v1; + rd["info"] = bdecode(ti->info_section()); + 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_hashes() == ti->info_hashes()); + torrent_status s = h.status(); +} +#endif + +TORRENT_TEST(merkle_trees) +{ + lt::add_torrent_params p; + p.ti = generate_torrent(); + p.save_path = "."; + + std::vector> piece_layers; + + for (file_index_t const i : p.ti->files().file_range()) + { + auto const pspan = p.ti->piece_layer(i); + piece_layers.emplace_back(pspan.begin(), pspan.end()); + } + + lt::session ses(settings()); + auto h = ses.add_torrent(p); + + h.save_resume_data(); + + save_resume_data_alert const* a = alert_cast( + wait_for_alert(ses, save_resume_data_alert::alert_type + , "merkle_trees")); + + TEST_CHECK(a); + if (a == nullptr) return; + + TEST_EQUAL(a->params.merkle_trees.size(), 3); + TEST_EQUAL(a->params.merkle_tree_mask.size(), 3); + + auto const pl = h.piece_layers(); + TEST_CHECK(pl.size() == piece_layers.size()); + + for (file_index_t const i : p.ti->files().file_range()) + { + auto const& one_layer = pl[std::size_t(int(i))]; + TEST_CHECK(one_layer.size() == piece_layers[std::size_t(int(i))].size() / lt::sha256_hash::size()); + for (int piece = 0; piece < int(one_layer.size()); ++piece) + TEST_CHECK(one_layer[std::size_t(piece)] == lt::sha256_hash(piece_layers[std::size_t(int(i))].data() + piece * lt::sha256_hash::size())); + auto const& m = a->params.merkle_tree_mask[i]; + TEST_CHECK(std::count(m.begin(), m.end(), true) == int(a->params.merkle_trees[i].size())); + } +} + +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_hashes().v1; + rd["info"] = bdecode(ti->info_section()); + 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_hashes() == ti->info_hashes()); +} + +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) + { + int const ret = ::truncate("test_resume" SEP "tmp2", 128 * 1024 + 10); + TEST_EQUAL(ret, 0); + } + + entry rd; + + rd["file-format"] = "libtorrent resume file"; + rd["file-version"] = 1; + rd["info-hash"] = ti->info_hashes().v1.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(make_address("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(make_address("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()); + + auto const now = lt::clock_type::now(); + torrent_status s = test_resume_flags(ses).status(); + default_tests(s, now); +#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()); + auto const now = lt::clock_type::now(); + torrent_status s = test_resume_flags(ses + , torrent_flags::seed_mode).status(); + default_tests(s, now); + TEST_EQUAL(s.flags & flags_mask, torrent_flags::seed_mode); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); +} + +TORRENT_TEST(seed_mode_no_verify_files) +{ + lt::session ses(settings()); + auto const now = lt::clock_type::now(); + torrent_status s = test_resume_flags(ses + , torrent_flags::seed_mode | torrent_flags::no_verify_files).status(); + default_tests(s, now); + // note that torrent_flags::no_verify_files is NOT set here + 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()); + auto const now = lt::clock_type::now(); + torrent_status s = test_resume_flags(ses, torrent_flags::upload_mode).status(); + default_tests(s, now); +#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()); + auto const now = lt::clock_type::now(); + torrent_status s = test_resume_flags(ses + , torrent_flags::share_mode).status(); + default_tests(s, now); +#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 + auto const now = lt::clock_type::now(); + torrent_status s = test_resume_flags(ses, torrent_flags::auto_managed).status(); + default_tests(s, now); +#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 + // auto managed torrents may have been paused by the time we get here, so + // filter out that flag + TEST_EQUAL((s.flags & flags_mask) & ~torrent_flags::paused, 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 + auto const now = lt::clock_type::now(); + torrent_status s = test_resume_flags(ses, torrent_flags::paused).status(); + default_tests(s, now); +#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 +} + +TORRENT_TEST(no_metadata) +{ + lt::session ses(settings()); + + add_torrent_params p; + p.info_hashes.v1 = sha1_hash("abababababababababab"); + p.save_path = "."; + p.name = "foobar"; + torrent_handle h = ses.add_torrent(p); + 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.info_hashes, p.info_hashes); + TEST_EQUAL(atp.name, "foobar"); + } +} + +template +void test_unfinished_pieces(Fun f) +{ + // create a torrent and complete files + std::shared_ptr ti = generate_torrent(true, true); + + add_torrent_params p; + p.info_hashes = ti->info_hashes(); + p.have_pieces.resize(ti->num_pieces(), true); + p.ti = ti; + p.save_path = "."; + + f(*ti, p); + + lt::session ses(settings()); + torrent_handle h = ses.add_torrent(p); + torrent_status s = h.status(); + TEST_EQUAL(s.info_hashes, ti->info_hashes()); + + if (s.state == torrent_status::seeding) return; + + print_alerts(ses, "ses"); + + for (int i = 0; i < 30; ++i) + { + std::this_thread::sleep_for(lt::milliseconds(100)); + s = h.status(); + print_alerts(ses, "ses"); + if (s.state == torrent_status::seeding) return; + } + + TEST_EQUAL(s.state, torrent_status::seeding); +} + +TORRENT_TEST(unfinished_pieces_pure_seed) +{ + test_unfinished_pieces([](torrent_info const&, add_torrent_params&){}); +} + +TORRENT_TEST(unfinished_pieces_check_all) +{ + test_unfinished_pieces([](torrent_info const&, add_torrent_params& atp) + { + atp.have_pieces.clear(); + }); +} + +TORRENT_TEST(unfinished_pieces_finished) +{ + // make sure that a piece that isn't maked as "have", but whose blocks are + // all downloaded gets checked and turn into "have". + test_unfinished_pieces([](torrent_info const& ti, add_torrent_params& atp) + { + atp.have_pieces.clear_bit(0_piece); + atp.unfinished_pieces[0_piece].resize(ti.piece_length() / 0x4000, true); + }); +} + +TORRENT_TEST(unfinished_pieces_all_finished) +{ + // make sure that a piece that isn't maked as "have", but whose blocks are + // all downloaded gets checked and turn into "have". + test_unfinished_pieces([](torrent_info const& ti, add_torrent_params& atp) + { + // we have none of the pieces + atp.have_pieces.clear_all(); + int const blocks_per_piece = ti.piece_length() / 0x4000; + + // but all pieces are downloaded + for (piece_index_t p : ti.piece_range()) + atp.unfinished_pieces[p].resize(blocks_per_piece, true); + }); +} + +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE +// this test relies on (and tests) the disk I/O being asynchronous. Since the +// posix_disk_io isn't, this test won't pass +TORRENT_TEST(resume_data_have_pieces) +{ + if (sizeof(void*) < 8) + { + // disable this test when disk I/O is not async + return; + } + + file_storage fs; + fs.add_file("tmp1", 128 * 1024 * 8); + lt::create_torrent t(fs, 128 * 1024); + + TEST_CHECK(t.num_pieces() > 0); + + std::vector piece_data(std::size_t(fs.piece_length()), 0); + aux::random_bytes(piece_data); + + sha1_hash const ph = lt::hasher(piece_data).final(); + for (auto const i : fs.piece_range()) + t.set_hash(i, ph); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + auto ti = std::make_shared(buf, from_span); + + lt::session ses(settings()); + lt::add_torrent_params atp; + atp.ti = ti; + atp.flags &= ~torrent_flags::paused; + atp.save_path = "."; + auto h = ses.add_torrent(atp); + wait_for_downloading(ses, ""); + h.add_piece(0_piece, piece_data.data()); + + h.save_resume_data(); + ses.pause(); + + auto const* rs = static_cast( + wait_for_alert(ses, save_resume_data_alert::alert_type)); + TEST_CHECK(rs != nullptr); + TEST_EQUAL(rs->params.unfinished_pieces.size(), 1); +} +#endif + +// See https://github.com/arvidn/libtorrent/issues/5174 +TORRENT_TEST(removed) +{ + lt::session ses(settings()); + std::shared_ptr ti = generate_torrent(); + add_torrent_params p; + p.ti = ti; + p.save_path = "."; + // we're _likely_ to trigger the condition, but not guaranteed. loop + // until we do. + bool triggered = false; + for (int i = 0; i < 10; i++) { + torrent_handle h = ses.add_torrent(p); + // this is asynchronous + ses.remove_torrent(h); + try { + h.save_resume_data(); + triggered = true; + } catch (std::exception const&) { + std::printf("failed to trigger condition, retrying\n"); + } + } + TEST_CHECK(triggered); + if (!triggered) return; + alert const* a = wait_for_alert(ses, save_resume_data_failed_alert::alert_type); + TEST_CHECK(a != nullptr); +} diff --git a/test/test_session.cpp b/test/test_session.cpp new file mode 100644 index 0000000..195530d --- /dev/null +++ b/test/test_session.cpp @@ -0,0 +1,582 @@ +/* + +Copyright (c) 2018, Steven Siloti +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2017-2018, 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/session.hpp" +#include "libtorrent/session_params.hpp" +#include +#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 "settings.hpp" + +#include +#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 +} + +#if TORRENT_ABI_VERSION < 3 +TORRENT_TEST(async_add_torrent_deprecated_magnet) +{ + settings_pack p = settings(); + p.set_int(settings_pack::alert_mask, ~0); + lt::session ses(p); + + add_torrent_params atp; + atp.info_hash = sha1_hash("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; + + TEST_EQUAL(a->params.info_hashes.v1, sha1_hash("abababababababababab")); +} +#endif + +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_hashes.v1.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_hashes.v1.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_hashes.v1.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); + + TEST_EQUAL(lt::find_metric_idx("utp.utp_packet_resend") + , lt::counters::utp_packet_resend); + TEST_EQUAL(lt::find_metric_idx("utp.utp_fast_retransmit") + , lt::counters::utp_fast_retransmit); +} + +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)); +} + +template +void test_save_restore(Set setup, Save s, Test t) +{ + entry st; + { + settings_pack p = settings(); + setup(p); + lt::session ses(p); + s(ses, st); + } + + { + // 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); + lt::session ses(read_session_params(buf)); + t(ses); + } +} + +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) { + st = write_session_params(ses.session_state()); + }, + [](lt::session& ses) { + // 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 + st = write_session_params(ses.session_state(~session::save_settings)); + }, + [](lt::session& ses) { + // 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), 3); + }); +} + +TORRENT_TEST(session_shutdown) +{ + lt::settings_pack pack; + lt::session ses(pack); +} + +TORRENT_TEST(save_state_fingerprint) +{ + lt::session_proxy p1; + lt::session_proxy p2; + + 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"); + auto const st = write_session_params_buf(ses.session_state()); + + pack.set_str(settings_pack::peer_fingerprint, "foobar"); + ses.apply_settings(pack); + TEST_EQUAL(ses.get_settings().get_str(settings_pack::peer_fingerprint), "foobar"); + + lt::session ses2(read_session_params(st)); + TEST_EQUAL(ses2.get_settings().get_str(settings_pack::peer_fingerprint), "AAA"); + + p1 = ses.abort(); + p2 = ses2.abort(); +} + +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()); +} + +namespace { + +lt::session_proxy test_move_session(lt::session ses) +{ + std::this_thread::sleep_for(lt::milliseconds(100)); + auto t = ses.get_torrents(); + return ses.abort(); +} +} + +TORRENT_TEST(move_session) +{ + lt::settings_pack pack; + lt::session ses(pack); + lt::session_proxy p = test_move_session(std::move(ses)); +} + +#if !defined TORRENT_DISABLE_LOGGING +#if !defined TORRENT_DISABLE_ALERT_MSG + +#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); +} + +#if TORRENT_ABI_VERSION <= 2 +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 + +#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 = 60; // this number is adjusted per version, an estimate + time_point const end_time = clock_type::now() + seconds(3); + 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 (expect: %d), count_portmap: %d (expect: %d)\n" + , count_listen, listen, count_portmap, 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 // TORRENT_DISABLE_ALERT_MSG +#endif + diff --git a/test/test_session_params.cpp b/test/test_session_params.cpp new file mode 100644 index 0000000..fd2034e --- /dev/null +++ b/test/test_session_params.cpp @@ -0,0 +1,287 @@ +/* + +Copyright (c) 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +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/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 "libtorrent/session_params.hpp" +#include "libtorrent/extensions.hpp" + +#include "test.hpp" +#include "setup_transfer.hpp" + +#include + +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( + settings_interface const& settings) + { + g_storage_constructor_invoked = true; + return dht::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); + p.set_int(settings_pack::dht_max_dht_items, 10000); + p.set_int(settings_pack::dht_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_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); + session_params const params1 = ses1.session_state(); + TEST_EQUAL(params1.settings.get_int(settings_pack::dht_max_dht_items), 10000); + TEST_EQUAL(params1.settings.get_int(settings_pack::dht_max_peers), 20000); + entry const e = write_session_params(params1); + + std::vector tmp; + bencode(std::back_inserter(tmp), e); + + session_params params2 = read_session_params(tmp); + TEST_EQUAL(params2.settings.get_int(settings_pack::dht_max_dht_items), 10000); + TEST_EQUAL(params2.settings.get_int(settings_pack::dht_max_peers), 20000); + + TEST_EQUAL(params2.dht_state.nids.size(), 1); + + if (params2.dht_state.nids.size() >= 1) + { + // not a chance the nid will be the fake initial ones + TEST_CHECK(params2.dht_state.nids[0].second != s.nids[0].second); + } +} +#endif + +namespace { + +settings_pack test_pack() +{ + settings_pack ret; + for (std::uint16_t i = 0; i < settings_pack::num_string_settings; ++i) + { + std::uint16_t const n = i | settings_pack::string_type_base; + if (name_for_setting(n) == std::string()) continue; + ret.set_str(n , std::to_string(i) + "__"); + } + for (std::uint16_t i = 0; i < settings_pack::num_int_settings; ++i) + { + std::uint16_t const n = i | settings_pack::int_type_base; + if (name_for_setting(n) == std::string()) continue; + ret.set_int(n, 1000000 + i); + } + for (std::uint16_t i = 0; i < settings_pack::num_bool_settings; ++i) + { + std::uint16_t const n = i | settings_pack::bool_type_base; + if (name_for_setting(n) == std::string()) continue; + ret.set_bool(n, i & 1); + } + return ret; +} + +dht::dht_state test_state() +{ + dht::dht_state ret; + auto a1 = make_address("1.2.3.4"); + auto a2 = make_address("1234:abcd:ef01::1"); + ret.nids = dht::node_ids_t{{a1, dht::generate_id(a1)}, {a2, dht::generate_id(a2)}}; + for (int i = 0; i < 50; ++i) + ret.nodes.push_back(rand_udp_ep(rand_v4)); + for (int i = 0; i < 50; ++i) + ret.nodes.push_back(rand_udp_ep(rand_v6)); + return ret; +} + +ip_filter test_ip_filter() +{ + ip_filter ret; + ret.add_rule(make_address("fe80::"), make_address("fe81::"), 1); + ret.add_rule(make_address("127.0.0.1"), make_address("127.255.255.255"), 1); + return ret; +} + +session_params test_params() +{ + session_params ret; + ret.settings = test_pack(); + ret.dht_state = test_state(); + for (int i = 0; i < 100; ++i) + ret.ext_state[std::to_string(i)] = std::string(std::to_string(i)); + ret.ip_filter = test_ip_filter(); + return ret; +} + +bool operator==(dht::dht_state const& lhs, dht::dht_state const& rhs) +{ + return lhs.nids == rhs.nids + && lhs.nodes == rhs.nodes + && lhs.nodes6 == rhs.nodes6 + ; +} + +bool operator==(lt::settings_pack const& lhs, lt::settings_pack const& rhs) +{ + for (std::uint16_t i = 0; i < settings_pack::num_string_settings; ++i) + if (lhs.get_str(i | settings_pack::string_type_base) != rhs.get_str(i | settings_pack::string_type_base)) return false; + for (std::uint16_t i = 0; i < settings_pack::num_int_settings; ++i) + if (lhs.get_int(i | settings_pack::int_type_base) != rhs.get_int(i | settings_pack::int_type_base)) return false; + for (std::uint16_t i = 0; i < settings_pack::num_bool_settings; ++i) + if (lhs.get_bool(i | settings_pack::bool_type_base) != rhs.get_bool(i | settings_pack::bool_type_base)) return false; + return true; +} + +void test_ip_filter(ip_filter const& f) +{ + TEST_EQUAL(f.access(make_address("fe7f::1")), 0); + TEST_EQUAL(f.access(make_address("fe80::1")), 1); + TEST_EQUAL(f.access(make_address("fe81::1")), 0); + TEST_EQUAL(f.access(make_address("127.0.0.0")), 0); + TEST_EQUAL(f.access(make_address("127.0.0.1")), 1); + TEST_EQUAL(f.access(make_address("127.255.0.1")), 1); + TEST_EQUAL(f.access(make_address("128.0.0.0")), 0); +} + +} // anonymous namespace + +TORRENT_TEST(session_params_ip_filter) +{ + session_params input; + input.ip_filter = test_ip_filter(); + + test_ip_filter(input.ip_filter); + + std::vector const buf = write_session_params_buf(input); + { + std::ofstream f("../session_state.test"); + f.write(buf.data(), static_cast(buf.size())); + } + session_params const output = read_session_params(buf); + + test_ip_filter(output.ip_filter); +} + +TORRENT_TEST(session_params_round_trip) +{ + session_params const input = test_params(); + + std::vector const buf = write_session_params_buf(input); + { + std::ofstream f("../session_state.test"); + f.write(buf.data(), static_cast(buf.size())); + } + session_params const output = read_session_params(buf); + + TEST_CHECK(input.settings == output.settings); + TEST_CHECK(input.dht_state == output.dht_state); + TEST_CHECK(input.ext_state == output.ext_state); + TEST_CHECK(input.ip_filter.export_filter() == output.ip_filter.export_filter()); +} + +#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..4d56159 --- /dev/null +++ b/test/test_settings_pack.cpp @@ -0,0 +1,321 @@ +/* + +Copyright (c) 2010, 2014-2020, Arvid Norberg +Copyright (c) 2016-2017, 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/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(non_default_settings(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(non_default_settings(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(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(non_default_settings(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::auto_manage_prefer_seeds, settings_pack::bool_type_base + 12); + 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); +} + +namespace { + +bool empty_pack(settings_pack const& sp) +{ + for (std::uint16_t i = 0; i < settings_pack::num_string_settings; ++i) + if (sp.has_val(i | settings_pack::string_type_base)) return false; + for (std::uint16_t i = 0; i < settings_pack::num_int_settings; ++i) + if (sp.has_val(i | settings_pack::int_type_base)) return false; + for (std::uint16_t i = 0; i < settings_pack::num_bool_settings; ++i) + if (sp.has_val(i | settings_pack::bool_type_base)) return false; + return true; +} + +} + +TORRENT_TEST(non_default_settings) +{ + aux::session_settings def; + settings_pack const p = non_default_settings(def); + TEST_CHECK(empty_pack(p)); +} + +TORRENT_TEST(non_default_settings2) +{ + aux::session_settings def; + def.set_int(settings_pack::max_out_request_queue, 1337); + settings_pack const p = non_default_settings(def); + TEST_EQUAL(p.get_int(settings_pack::max_out_request_queue), 1337); +} + +TORRENT_TEST(save_settings_to_dict) +{ + settings_pack p; + p.set_str(settings_pack::peer_fingerprint, "abc"); + p.set_int(settings_pack::max_out_request_queue, 1337); + p.set_bool(settings_pack::send_redundant_have, false); + + entry e; + save_settings_to_dict(p, e.dict()); + std::string buf; + bencode(std::back_inserter(buf), e); + TEST_EQUAL(buf, "d21:max_out_request_queuei1337e16:peer_fingerprint3:abc19:send_redundant_havei0ee"); +} + +// MSVC doesn't support making these arrays constexpr yet (which is required to +// initialize them before global constructors) +#if !defined _MSC_VER +namespace { +// make sure a global constructor has access to the default values, to +// initialize itself with +lt::aux::session_settings g_sett; +} + +TORRENT_TEST(global_constructors) +{ + TEST_CHECK(g_sett.get_int(lt::settings_pack::aio_threads) > 0); +} +#endif diff --git a/test/test_sha1_hash.cpp b/test/test_sha1_hash.cpp new file mode 100644 index 0000000..6e61d7e --- /dev/null +++ b/test/test_sha1_hash.cpp @@ -0,0 +1,147 @@ +/* + +Copyright (c) 2015-2017, 2019, Arvid Norberg +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" +#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_similar_torrent.cpp b/test/test_similar_torrent.cpp new file mode 100644 index 0000000..676ba2b --- /dev/null +++ b/test/test_similar_torrent.cpp @@ -0,0 +1,347 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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" // for load_file +#include "test_utils.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/bencode.hpp" + +namespace { + +using lt::operator""_bit; +using similar_test_t = lt::flags::bitfield_flag; + +namespace st +{ + constexpr similar_test_t no_files = 0_bit; + constexpr similar_test_t seed_mode = 1_bit; + constexpr similar_test_t alt_b = 2_bit; + constexpr similar_test_t alt_a = 3_bit; + constexpr similar_test_t magnet = 4_bit; + constexpr similar_test_t collection = 5_bit; +} + +std::array test( + similar_test_t const sflags + , lt::create_flags_t const cflags1 + , lt::create_flags_t const cflags2) +{ + lt::error_code ec; + lt::create_directories("./test-torrent-1", ec); + TORRENT_ASSERT(!ec); + + std::vector A(0x8000); + std::vector B(0x5000); + lt::aux::random_bytes(A); + lt::aux::random_bytes(B); + + std::vector A_alt(0x8000); + lt::aux::random_bytes(A_alt); + std::vector B_alt(0x5000); + lt::aux::random_bytes(B_alt); + + { + ofstream f("./test-torrent-1/A"); + f.write(A.data(), int(A.size())); + } + + { + ofstream f("./test-torrent-1/B"); + f.write(B.data(), int(B.size())); + } + + auto t1 = [&] { + lt::file_storage fs; + fs.add_file(ec, "test-torrent-1/A", std::int64_t(A.size())); + fs.add_file(ec, "test-torrent-1/B", std::int64_t(B.size())); + lt::create_torrent t(fs, 0, cflags1); + lt::set_piece_hashes(t, "."); + if (sflags & st::collection) + t.add_collection("test collection"); + std::vector torrent; + lt::bencode(back_inserter(torrent), t.generate()); + return std::make_shared(torrent, lt::from_span); + }(); + + lt::create_directories("test-torrent-2", ec); + TORRENT_ASSERT(!ec); + + if (sflags & st::alt_a) + { + ofstream f("test-torrent-2/A"); + f.write(A_alt.data(), int(A_alt.size())); + } + else + { + ofstream f("test-torrent-2/A"); + f.write(A.data(), int(A.size())); + } + + if (sflags & st::alt_b) + { + ofstream f("test-torrent-2/B"); + f.write(B_alt.data(), int(B_alt.size())); + } + else + { + ofstream f("test-torrent-2/B"); + f.write(B.data(), int(B.size())); + } + + auto t2 = [&] { + lt::file_storage fs; + fs.add_file(ec, "test-torrent-2/A", std::int64_t(A.size())); + fs.add_file(ec, "test-torrent-2/B", std::int64_t(B.size())); + lt::create_torrent t(fs, 0, cflags2); + lt::set_piece_hashes(t, "."); + if (sflags & st::collection) + t.add_collection("test collection"); + else + t.add_similar_torrent(t1->info_hash()); + std::vector torrent; + lt::bencode(back_inserter(torrent), t.generate()); + return std::make_shared(torrent, lt::from_span); + }(); + + if (sflags & st::no_files) + { + lt::remove_all("test-torrent-1", ec); + TORRENT_ASSERT(!ec); + } + + lt::remove_all("test-torrent-2", ec); + TORRENT_ASSERT(!ec); + + lt::settings_pack pack; + pack.set_bool(lt::settings_pack::enable_dht, false); + pack.set_bool(lt::settings_pack::enable_lsd, false); + pack.set_bool(lt::settings_pack::enable_natpmp, false); + pack.set_bool(lt::settings_pack::enable_upnp, false); + pack.set_int(lt::settings_pack::alert_mask, lt::alert::all_categories); + lt::session ses(pack); + + lt::add_torrent_params atp; + atp.flags &= ~lt::torrent_flags::auto_managed; + atp.flags &= ~lt::torrent_flags::paused; + + if (sflags & st::seed_mode) + atp.flags |= lt::torrent_flags::seed_mode; + + atp.ti = t1; + atp.save_path = "."; + lt::torrent_handle h1 = ses.add_torrent(atp); + + wait_for_seeding(ses, "1"); + + if (sflags & st::magnet) + { + atp.ti.reset(); + atp.info_hashes = t2->info_hashes(); + } + else + { + atp.ti = t2; + } + atp.flags &= ~lt::torrent_flags::seed_mode; + lt::torrent_handle h2 = ses.add_torrent(atp); + + if (sflags & st::magnet) + { + h2.set_metadata(t2->info_section()); + } + + std::array completed_files{{false, false}}; + + // wait for torrent 2 to either start downloading or finish. While waiting, + // record which files it completes + auto const start_time = lt::clock_type::now(); + for (;;) + { + auto const done = print_alerts(ses, "2", false, false + , [&](lt::alert const* al) + { + if (auto const* sc = lt::alert_cast(al)) + { + return sc->handle == h2 + && (sc->state == lt::torrent_status::seeding + || sc->state == lt::torrent_status::finished + || sc->state == lt::torrent_status::downloading); + } + if (auto const* fc = lt::alert_cast(al)) + { + if (fc->handle == h2) + completed_files[std::size_t(static_cast(fc->index))] = true; + } + return false; + }, false); + if (done) break; + + if (lt::clock_type::now() - start_time > lt::seconds(5)) + { + TEST_ERROR("timeout"); + break; + } + ses.wait_for_alert(lt::seconds(5)); + } + + return completed_files; +} + +} + +using bools = std::array; + +auto const v1 = lt::create_torrent::v1_only; +auto const v2 = lt::create_torrent::v2_only; +auto const canon = lt::create_torrent::canonical_files; + +TORRENT_TEST(shared_files_v1_no_pad) +{ + // the first file will be aligned, and since its size is an even multiple of + // the piece size, the second file will too + TEST_CHECK(test({}, v1, v1) == bools({true, true})); +} + +TORRENT_TEST(shared_files_v1) +{ + // with canonical files, all files are aligned + TEST_CHECK(test({}, v1 | canon, v1 | canon) == bools({true, true})); +} + +TORRENT_TEST(shared_files_v1_collection) +{ + TEST_CHECK(test(st::collection, v1 | canon, v1 | canon) == bools({true, true})); +} + +TORRENT_TEST(shared_files_v1_magnet) +{ + TEST_CHECK(test(st::magnet, v1 | canon, v1 | canon) == bools({true, true})); +} + +TORRENT_TEST(shared_files_v1_magnet_collection) +{ + TEST_CHECK(test(st::magnet | st::collection, v1 | canon, v1 | canon) == bools({true, true})); +} + +TORRENT_TEST(shared_files_v2_magnet) +{ + TEST_CHECK(test(st::magnet, v2, v2) == bools({true, true})); +} + +TORRENT_TEST(shared_files_v2_magnet_collection) +{ + TEST_CHECK(test(st::magnet | st::collection, v2, v2) == bools({true, true})); +} + +TORRENT_TEST(shared_files_hybrid_magnet) +{ + TEST_CHECK(test(st::magnet, {}, {}) == bools({true, true})); +} + +TORRENT_TEST(shared_files_v1_v2_magnet) +{ + TEST_CHECK(test(st::magnet, v1 | canon, v2) == bools({false, false})); +} + +TORRENT_TEST(shared_files_seed_mode_v1) +{ + TEST_CHECK(test(st::seed_mode, v1 | canon, v1 | canon) == bools({true, true})); +} + +TORRENT_TEST(shared_files_seed_mode_v1_no_files) +{ + // no files on disk, just an (incorrect) promise of beeing in seed mode + // creating the hard links will fail + TEST_CHECK(test(st::no_files | st::seed_mode, v1 | canon, v1 | canon) == bools({false, false})); +} + +TORRENT_TEST(single_shared_files_v1_b) +{ + TEST_CHECK(test(st::alt_b, v1 | canon, v1 | canon) == bools({true, false})); +} + +TORRENT_TEST(single_shared_files_v1_a) +{ + TEST_CHECK(test(st::alt_a, v1 | canon, v1 | canon) == bools({false, true})); +} + +TORRENT_TEST(shared_files_v1_v2) +{ + // v1 piece hashes cannot be compared to the v2 merkle roots + TEST_CHECK(test({}, v1 | canon, v2) == bools({false, false})); +} + +TORRENT_TEST(shared_files_v2) +{ + TEST_CHECK(test({}, v2, v2) == bools({true, true})); +} + +TORRENT_TEST(shared_files_v2_collection) +{ + TEST_CHECK(test(st::collection, v2, v2) == bools({true, true})); +} + +TORRENT_TEST(shared_files_seed_mode_v2) +{ + TEST_CHECK(test(st::seed_mode, v2, v2) == bools({true, true})); +} + +TORRENT_TEST(shared_files_seed_mode_v2_no_files) +{ + TEST_CHECK(test(st::no_files | st::seed_mode, v2, v2) == bools({false, false})); +} + +TORRENT_TEST(single_shared_files_v2_b) +{ + TEST_CHECK(test(st::alt_b, v2, v2) == bools({true, false})); +} + +TORRENT_TEST(single_shared_files_v2_a) +{ + TEST_CHECK(test(st::alt_a, v2, v2) == bools({false, true})); +} + +TORRENT_TEST(shared_files_hybrid) +{ + TEST_CHECK(test({}, {}, {}) == bools({true, true})); +} + +TORRENT_TEST(shared_files_hybrid_v2) +{ + TEST_CHECK(test({}, {}, v2) == bools({true, true})); +} diff --git a/test/test_sliding_average.cpp b/test/test_sliding_average.cpp new file mode 100644 index 0000000..946a6f0 --- /dev/null +++ b/test/test_sliding_average.cpp @@ -0,0 +1,118 @@ +/* + +Copyright (c) 2014-2019, Arvid Norberg +Copyright (c) 2018, 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/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..8267a02 --- /dev/null +++ b/test/test_socket_io.cpp @@ -0,0 +1,218 @@ +/* + +Copyright (c) 2014-2017, 2019, Arvid Norberg +Copyright (c) 2016-2017, 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 "setup_transfer.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/socket.hpp" + +#include + +using namespace lt; +using namespace lt::aux; + +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..3d016c5 --- /dev/null +++ b/test/test_span.cpp @@ -0,0 +1,153 @@ +/* + +Copyright (c) 2017, Steven Siloti +Copyright (c) 2017-2019, Arvid Norberg +Copyright (c) 2017-2018, 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/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..5c7df17 --- /dev/null +++ b/test/test_ssl.cpp @@ -0,0 +1,635 @@ +/* + +Copyright (c) 2018, Steven Siloti +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2016, 2018-2019, Alden Torres +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/ssl.hpp" + +#include "test.hpp" +#include "test_utils.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" + +#if TORRENT_USE_SSL + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#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 == ssl::error::get_ssl_category().name() + || cat == ssl::error::get_stream_category().name()) + ++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 == ssl::error::get_ssl_category().name() + || cat == ssl::error::get_stream_category().name()) + ++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().c_str() + , 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(session_params{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(session_params{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().c_str(), port); + tor1.connect_peer(tcp::endpoint(make_address("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); + } + + std::string const now = time_now_string(); + std::printf("%s: EXPECT: %s\n", now.c_str(), test.expected_to_complete ? "SUCCEESS" : "FAILURE"); + std::printf("%s: RESULT: %s\n", now.c_str(), 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(); +} + +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 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_context 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::tls); + + ctx.set_options(context::default_workarounds + | context::no_sslv2 + | context::no_sslv3 + | 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::size_t, context::password_purpose) { return "test"; } + , ec); + if (ec) + { + std::printf("Failed to set certificate passphrase: %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; + } + } + + ssl::stream ssl_sock(ios, ctx); + + std::printf("connecting 127.0.0.1:%d\n", port); + ssl_sock.lowest_layer().connect(tcp::endpoint( + make_address_v4("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_hashes().v1); + std::printf("SNI: %s\n", name.c_str()); + ssl::set_host_name(ssl::get_handle(ssl_sock), name, ec); + TEST_CHECK(!ec); + } + 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()); + ssl::set_host_name(ssl::get_handle(ssl_sock), name, ec); + TEST_CHECK(!ec); + } + + std::printf("SSL handshake\n"); + ssl_sock.handshake(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_hashes().v1[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_hashes().v1.data(), 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(session_params{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_SSL_PEERS + diff --git a/test/test_stack_allocator.cpp b/test/test_stack_allocator.cpp new file mode 100644 index 0000000..f8a3157 --- /dev/null +++ b/test/test_stack_allocator.cpp @@ -0,0 +1,142 @@ +/* + +Copyright (c) 2016, Mokhtar Naamani +Copyright (c) 2016-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 "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..1e01cc4 --- /dev/null +++ b/test/test_stat_cache.cpp @@ -0,0 +1,88 @@ +/* + +Copyright (c) 2010, 2014-2017, 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 "libtorrent/stat_cache.hpp" +#include "libtorrent/error_code.hpp" +#include "test.hpp" +#include "test_utils.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(3_file, error_code(boost::system::errc::permission_denied, generic_category())); + ec.clear(); + TEST_EQUAL(sc.get_filesize(3_file, fs, save_path, ec), stat_cache::file_error); + TEST_EQUAL(ec, error_code(boost::system::errc::permission_denied, generic_category())); + + sc.set_error(3_file, error_code(boost::system::errc::no_such_file_or_directory, generic_category())); + ec.clear(); + TEST_EQUAL(sc.get_filesize(3_file, 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(3_file, 101); + TEST_EQUAL(sc.get_filesize(3_file, fs, save_path, ec), 101); + TEST_CHECK(!ec); + + sc.set_error(11_file, error_code(boost::system::errc::broken_pipe, generic_category())); + ec.clear(); + TEST_EQUAL(sc.get_filesize(11_file, 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(13_file, error_code(boost::system::errc::no_such_file_or_directory, generic_category())); + TEST_EQUAL(sc.get_filesize(13_file, 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(15_file, 1000); + TEST_CHECK(sc.get_filesize(15_file, 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..348458f --- /dev/null +++ b/test/test_storage.cpp @@ -0,0 +1,1827 @@ +/* + +Copyright (c) 2005, 2007-2010, 2012-2020, Arvid Norberg +Copyright (c) 2016, 2018, 2020, Alden Torres +Copyright (c) 2016, Vladimir Golovnev +Copyright (c) 2016-2018, Steven Siloti +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/mmap_storage.hpp" +#include "libtorrent/aux_/posix_storage.hpp" +#include "libtorrent/aux_/file_view_pool.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/session_params.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_/mmap_disk_job.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/aux_/storage_utils.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/mmap_disk_io.hpp" +#include "libtorrent/posix_disk_io.hpp" +#include "libtorrent/flags.hpp" + +#include +#include // for bind + +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +using namespace std::placeholders; +using namespace lt; + +#if ! TORRENT_HAVE_MMAP && ! TORRENT_HAVE_MAP_VIEW_OF_FILE +namespace libtorrent { +namespace aux { + struct file_view_pool {}; +} +} +#endif + +namespace { + +using lt::aux::posix_storage; +using lt::aux::mmap_disk_job; + +constexpr int piece_size = 16 * 1024 * 16; +constexpr int half = piece_size / 2; + +void delete_dirs(std::string path) +{ + path = complete(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)); +} + +void on_check_resume_data(lt::status_t const status, storage_error const& error, bool* done, bool* oversized) +{ + std::cout << time_now_string() << " on_check_resume_data ret: " + << static_cast(status); + if ((status & lt::status_t::oversized_file) != status_t{}) + { + std::cout << " oversized file(s) - "; + *oversized = true; + } + else + { + *oversized = false; + } + + switch (status & ~lt::status_t::mask) + { + case lt::status_t::no_error: + std::cout << " success" << std::endl; + break; + case lt::status_t::fatal_disk_error: + std::cout << " disk error: " << error.ec.message() + << " file: " << error.file() << std::endl; + break; + case lt::status_t::need_full_check: + std::cout << " need full check" << std::endl; + break; + case lt::status_t::file_exist: + std::cout << " file exist" << std::endl; + break; + case lt::status_t::mask: + case lt::status_t::oversized_file: + 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().c_str(), call, ret, ec.ec.message().c_str() + , static_cast(ec.file()), operation_name(ec.operation)); +} + +void run_until(io_context& ios, bool const& done) +{ + while (!done) + { + ios.restart(); + ios.run_one(); + std::cout << time_now_string() << " done: " << done << std::endl; + } +} + +std::shared_ptr setup_torrent_info(file_storage& fs + , std::vector& buf) +{ + fs.add_file(combine_path("temp_storage", "test1.tmp"), 0x8000); + fs.add_file(combine_path("temp_storage", combine_path("folder1", "test2.tmp")), 0x8000); + 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"))), 0x8000); + lt::create_torrent t(fs, 0x4000); + + sha1_hash h = hasher(std::vector(0x4000, 0)).final(); + for (piece_index_t i(0); i < 6_piece; ++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()); + throw system_error(ec); + } + + return info; +} + +template +std::shared_ptr make_storage(storage_params const& p + , aux::file_view_pool& fp); + +#if TORRENT_HAVE_MMAP +template <> +std::shared_ptr make_storage(storage_params const& p + , aux::file_view_pool& fp) +{ + return std::make_shared(p, fp); +} +#else +template <> +std::shared_ptr make_storage(storage_params const& p + , aux::file_view_pool& fp) = delete; +#endif + +template <> +std::shared_ptr make_storage(storage_params const& p + , aux::file_view_pool&) +{ + return std::make_shared(p); +} + +template +std::shared_ptr setup_torrent(file_storage& fs + , aux::file_view_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 + }; + auto s = make_storage(p, fp); + + // allocate the files and create the directories + storage_error se; + s->initialize(set, se); + if (se) + { + TEST_ERROR(se.ec.message().c_str()); + std::printf("mmap_storage::initialize %s: %d\n" + , se.ec.message().c_str(), static_cast(se.file())); + throw system_error(se.ec); + } + + return s; +} + +#if TORRENT_HAVE_MMAP +int writev(std::shared_ptr s + , aux::session_settings const& sett + , span bufs + , piece_index_t const piece, int const offset + , aux::open_mode_t const mode + , storage_error& error) +{ + return s->writev(sett, bufs, piece, offset, mode, disk_job_flags_t{}, error); +} + +int readv(std::shared_ptr s + , aux::session_settings const& sett + , span bufs + , piece_index_t piece + , int const offset + , aux::open_mode_t mode + , storage_error& ec) +{ + return s->readv(sett, bufs, piece, offset, mode, disk_job_flags_t{}, ec); +} + +void release_files(std::shared_ptr s, storage_error& ec) +{ + s->release_files(ec); +} +#endif + +int writev(std::shared_ptr s + , aux::session_settings const& sett + , span bufs + , piece_index_t const piece + , int const offset + , aux::open_mode_t + , storage_error& error) +{ + return s->writev(sett, bufs, piece, offset, error); +} + +int readv(std::shared_ptr s + , aux::session_settings const& sett + , span bufs + , piece_index_t piece + , int offset + , aux::open_mode_t + , storage_error& ec) +{ + return s->readv(sett, bufs, piece, offset, ec); +} + +void release_files(std::shared_ptr, storage_error&) {} + +std::vector new_piece(std::size_t const size) +{ + std::vector ret(size); + aux::random_bytes(ret); + return ret; +} + +template +void run_storage_tests(std::shared_ptr info + , file_storage& fs + , lt::storage_mode_t storage_mode) +{ + TORRENT_ASSERT(fs.num_files() > 0); + { + error_code ec; + create_directory(complete("temp_storage"), ec); + if (ec) std::cout << "create_directory '" << complete("temp_storage") + << "': " << ec.message() << std::endl; + } + int const num_pieces = fs.num_pieces(); + TEST_EQUAL(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; + + std::vector piece(piece_size); + + { + // avoid having two storages use the same files + aux::file_view_pool fp; + boost::asio::io_context ios; + aux::vector priorities; + sha1_hash info_hash; + std::string const cwd = current_working_directory(); + storage_params p{ + fs, + nullptr, + cwd, + storage_mode, + priorities, + info_hash + }; + auto s = make_storage(p, fp); + + storage_error ec; + s->initialize(set, ec); + TEST_CHECK(!ec); + if (ec) print_error("initialize", 0, ec); + + int ret = 0; + + // write piece 1 (in slot 0) + iovec_t iov = span(piece1).first(half); + + ret = writev(s, set, iov, 0_piece, 0, aux::open_mode::write, ec); + TEST_EQUAL(ret, int(iov.size())); + if (ret != int(iov.size())) print_error("writev", ret, ec); + + iov = span(piece1).last(half); + ret = writev(s, set, iov, 0_piece, half, aux::open_mode::write, ec); + TEST_EQUAL(ret, int(iov.size())); + if (ret != int(iov.size())) print_error("writev", ret, ec); + + // test unaligned read (where the bytes are aligned) + iov = span(piece).subspan(3, piece_size - 9); + ret = readv(s, set, iov, 0_piece, 3, aux::open_mode::write, ec); + TEST_EQUAL(ret, int(iov.size())); + if (ret != int(iov.size())) print_error("readv",ret, ec); + TEST_CHECK(iov == span(piece1).subspan(3, piece_size - 9)); + + // test unaligned read (where the bytes are not aligned) + iov = span(piece).first(piece_size - 9); + ret = readv(s, set, iov, 0_piece, 3, aux::open_mode::write, ec); + TEST_EQUAL(ret, int(iov.size())); + if (ret != int(iov.size())) print_error("readv", ret, ec); + TEST_CHECK(iov == span(piece1).subspan(3, piece_size - 9)); + + // verify piece 1 + iov = piece; + ret = readv(s, set, iov, 0_piece, 0, aux::open_mode::write, ec); + TEST_EQUAL(ret, int(iov.size())); + if (ret != int(iov.size())) print_error("readv", ret, ec); + TEST_CHECK(piece == piece1); + + // do the same with piece 0 and 2 (in slot 1 and 2) + iov = piece0; + ret = writev(s, set, iov, 1_piece, 0, aux::open_mode::write, ec); + TEST_EQUAL(ret, int(iov.size())); + if (ret != int(iov.size())) print_error("writev", ret, ec); + + iov = piece2; + ret = writev(s, set, iov, 2_piece, 0, aux::open_mode::write, ec); + TEST_EQUAL(ret, int(iov.size())); + if (ret != int(iov.size())) print_error("writev", ret, ec); + + // verify piece 0 and 2 + iov = piece; + ret = readv(s, set, iov, 1_piece, 0, aux::open_mode::write, ec); + TEST_EQUAL(ret, int(iov.size())); + if (ret != int(iov.size())) print_error("readv", ret, ec); + TEST_CHECK(piece == piece0); + + iov = piece; + ret = readv(s, set, iov, 2_piece, 0, aux::open_mode::write, ec); + TEST_EQUAL(ret, int(iov.size())); + if (ret != int(piece_size)) print_error("readv", ret, ec); + TEST_CHECK(piece == piece2); + + release_files(s, ec); + } +} + +template +void test_remove(std::string const& test_path) +{ + delete_dirs("temp_storage"); + + file_storage fs; + std::vector buf; + aux::file_view_pool fp; + io_context ios; + + aux::session_settings set; + auto 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"))))); + + buf.resize(0x4000); + iovec_t b = {&buf[0], 0x4000}; + storage_error se; + writev(s, set, b, 2_piece, 0, aux::open_mode::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; + error_code ec; + stat_file(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder1", "test2.tmp"))), &st, ec); + + // if the storage truncates the file to the full size, it's 8, otherwise it's + // 4 + TEST_CHECK(st.file_size == 0x8000 || st.file_size == 0x4000); + + writev(s, set, b, 0_piece, 0, aux::open_mode::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); + + // if the storage truncates the file to the full size, it's 8, otherwise it's + // 4 + TEST_CHECK(st.file_size == 0x8000 || st.file_size == 0x4000); + + 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("mmap_storage::delete_files %s: %d\n" + , se.ec.message().c_str(), static_cast(se.file())); + } + + TEST_CHECK(!exists(combine_path(test_path, "temp_storage"))); +} + +template +void test_rename(std::string const& test_path) +{ + delete_dirs("temp_storage"); + + file_storage fs; + std::vector buf; + aux::file_view_pool fp; + io_context ios; + aux::session_settings set; + + auto 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(0_file); + 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(0_file, "new_filename", se); + if (se.ec) + { + std::printf("mmap_storage::rename_file failed: %s\n" + , se.ec.message().c_str()); + } + TEST_CHECK(!se.ec); + + TEST_EQUAL(s->files().file_path(0_file), "new_filename"); +} + +using lt::operator""_bit; +using check_files_flag_t = lt::flags::bitfield_flag; + +constexpr check_files_flag_t sparse = 0_bit; +constexpr check_files_flag_t test_oversized = 1_bit; +constexpr check_files_flag_t zero_prio = 2_bit; + +void test_check_files(check_files_flag_t const flags + , lt::disk_io_constructor_type const disk_constructor) +{ + std::string const test_path = current_working_directory(); + std::shared_ptr info; + + error_code ec; + constexpr int piece_size_check = 16 * 1024; + delete_dirs("temp_storage"); + + 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); + t.set_hash(0_piece, hasher(piece0).final()); + t.set_hash(1_piece, sha1_hash::max()); + t.set_hash(2_piece, sha1_hash::max()); + t.set_hash(3_piece, hasher(piece2).final()); + + create_directory(combine_path(test_path, "temp_storage"), ec); + if (ec) std::cout << "create_directory: " << ec.message() << std::endl; + + if (flags & test_oversized) + piece2.push_back(0x42); + + ofstream(combine_path(test_path, combine_path("temp_storage", "test1.tmp")).c_str()) + .write(piece0.data(), std::streamsize(piece0.size())); + ofstream(combine_path(test_path, combine_path("temp_storage", "test3.tmp")).c_str()) + .write(piece2.data(), std::streamsize(piece2.size())); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + info = std::make_shared(buf, ec, from_span); + + aux::session_settings set; + boost::asio::io_context ios; + counters cnt; + + aux::session_settings sett; + sett.set_int(settings_pack::aio_threads, 1); + std::unique_ptr io = disk_constructor(ios, sett, cnt); + + aux::vector priorities; + + if (flags & zero_prio) + priorities.resize(std::size_t(info->num_files()), download_priority_t{}); + + sha1_hash info_hash; + storage_params p{ + fs, + nullptr, + test_path, + (flags & sparse) ? storage_mode_sparse : storage_mode_allocate, + priorities, + info_hash + }; + + auto st = io->new_torrent(std::move(p), std::shared_ptr()); + + bool done = false; + bool oversized = false; + add_torrent_params frd; + aux::vector links; + io->async_check_files(st, &frd, links + , std::bind(&on_check_resume_data, _1, _2, &done, &oversized)); + io->submit_jobs(); + ios.restart(); + run_until(ios, done); + + TEST_EQUAL(oversized, bool(flags & test_oversized)); + + for (auto const i : info->piece_range()) + { + done = false; + io->async_hash(st, i, {} + , disk_interface::sequential_access | disk_interface::volatile_read | disk_interface::v1_hash + , std::bind(&on_piece_checked, _1, _2, _3, &done)); + io->submit_jobs(); + ios.restart(); + run_until(ios, done); + } + + io->abort(true); +} + +// TODO: 2 split this test up into smaller parts +template +void run_test() +{ + std::string const 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); + + delete_dirs("temp_storage"); + + 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, create_torrent::v1_only); + TEST_CHECK(t.num_pieces() == 4); + t.set_hash(0_piece, hasher(piece0).final()); + t.set_hash(1_piece, hasher(piece1).final()); + t.set_hash(2_piece, hasher(piece2).final()); + t.set_hash(3_piece, hasher(piece3).final()); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + info = std::make_shared(buf, from_span); + + // run_storage_tests writes piece 0, 1 and 2. not 3 + run_storage_tests(info, fs, storage_mode_sparse); + + // make sure the files have the correct size + std::string const base = complete("temp_storage"); + + // 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"))); + + delete_dirs("temp_storage"); +} + +#if TORRENT_HAVE_MMAP +TORRENT_TEST(check_files_sparse_mmap) +{ + test_check_files(sparse | zero_prio, lt::mmap_disk_io_constructor); +} + +TORRENT_TEST(check_files_oversized_mmap_zero_prio) +{ + test_check_files(sparse | zero_prio | test_oversized, lt::mmap_disk_io_constructor); +} + +TORRENT_TEST(check_files_oversized_mmap) +{ + test_check_files(sparse | test_oversized, lt::mmap_disk_io_constructor); +} + + +TORRENT_TEST(check_files_allocate_mmap) +{ + test_check_files(zero_prio, lt::mmap_disk_io_constructor); +} +#endif +TORRENT_TEST(check_files_sparse_posix) +{ + test_check_files(sparse | zero_prio, lt::posix_disk_io_constructor); +} + +TORRENT_TEST(check_files_oversized_zero_prio_posix) +{ + test_check_files(sparse | zero_prio | test_oversized, lt::posix_disk_io_constructor); +} + +TORRENT_TEST(check_files_oversized_posix) +{ + test_check_files(sparse | test_oversized, lt::posix_disk_io_constructor); +} + + +TORRENT_TEST(check_files_allocate_posix) +{ + test_check_files(zero_prio, lt::posix_disk_io_constructor); +} + +#if TORRENT_HAVE_MMAP +TORRENT_TEST(rename_mmap_disk_io) +{ + test_rename(current_working_directory()); +} + +TORRENT_TEST(remove_mmap_disk_io) +{ + test_remove(current_working_directory()); +} +#endif + +TORRENT_TEST(rename_posix_disk_io) +{ + test_rename(current_working_directory()); +} + +TORRENT_TEST(remove_posix_disk_io) +{ + test_remove(current_working_directory()); +} + +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; + delete_dirs("tmp1"); + + create_directory(combine_path(test_path, "tmp1"), ec); + if (ec) std::cout << "create_directory '" << combine_path(test_path, "tmp1") + << "': " << ec.message() << std::endl; + ofstream file(combine_path(test_path, "tmp1/temporary").c_str()); + std::shared_ptr t = ::create_torrent(&file); + file.close(); + TEST_CHECK(exists(complete("tmp1/temporary"))); + if (!exists(complete("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); + } + delete_dirs("tmp1"); +} + +} // 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; + delete_dirs("tmp2"); + create_directory(combine_path(test_path, "tmp2"), ec); + if (ec) std::cout << "create_directory: " << ec.message() << std::endl; + 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(0_file, "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 (std::size_t i = 0; i < static_cast(num_bufs); ++i) + { + std::size_t const len = static_cast(num_bufs) * (i + 1); + iov[i] = { new char[len], static_cast(len) }; + } +} + +// 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); +} + +#if TORRENT_HAVE_MMAP +TORRENT_TEST(mmap_disk_io) { run_test(); } +#endif +TORRENT_TEST(posix_disk_io) { run_test(); } + +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(aux::calc_num_pieces(fs)); + 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}, 0_piece, 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[0_file].size(), 3); + TEST_EQUAL(fop.m_file_data[1_file].size(), 9); + TEST_EQUAL(fop.m_file_data[2_file].size(), 81); + TEST_EQUAL(fop.m_file_data[3_file].size(), 6561); + + TEST_CHECK(check_pattern(fop.m_file_data[0_file], 0)); + TEST_CHECK(check_pattern(fop.m_file_data[1_file], 3)); + TEST_CHECK(check_pattern(fop.m_file_data[2_file], 3 + 9)); + TEST_CHECK(check_pattern(fop.m_file_data[3_file], 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, 0_piece, 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[0_file].size(), 3); + TEST_EQUAL(fop.m_file_data[1_file].size(), 9); + TEST_EQUAL(fop.m_file_data[2_file].size(), 81); + TEST_EQUAL(fop.m_file_data[3_file].size(), 6561); + + TEST_CHECK(check_pattern(fop.m_file_data[0_file], 0)); + TEST_CHECK(check_pattern(fop.m_file_data[1_file], 3)); + TEST_CHECK(check_pattern(fop.m_file_data[2_file], 3 + 9)); + TEST_CHECK(check_pattern(fop.m_file_data[3_file], 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, 0_piece, 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, 0_piece, 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(2_file); + 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, 0_piece, 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(aux::calc_num_pieces(fs)); + 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, 0_piece, 0, ec, std::ref(fop)); + + TEST_EQUAL(ret, fs.total_size()); + TEST_CHECK(check_pattern(buf, 0)); +} + +template +void 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 = complete("temp_storage"); + delete_dirs(test_path); + + aux::session_settings set; + file_storage fs; + std::vector buf; + aux::file_view_pool fp; + io_context ios; + auto s = setup_torrent(fs, fp, buf, save_path, set); + + iovec_t const b = {&buf[0], 4}; + storage_error se; + TEST_EQUAL(se.ec, boost::system::errc::success); + writev(s, set, b, 1_piece, 0, aux::open_mode::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")))); + TEST_EQUAL(se.ec, boost::system::errc::success); + + s->move_storage(save_path, move_flags_t::always_replace_files, se); + TEST_EQUAL(se.ec, boost::system::errc::success); + std::cerr << "file: " << se.file() << '\n'; + std::cerr << "op: " << int(se.operation) << '\n'; + std::cerr << "ec: " << se.ec.message() << '\n'; + + 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")))); +} + +template +void test_move_storage_into_self() +{ + std::string const save_path = current_working_directory(); + delete_dirs("temp_storage"); + + aux::session_settings set; + file_storage fs; + std::vector buf; + aux::file_view_pool fp; + io_context ios; + auto s = setup_torrent(fs, fp, buf, save_path, set); + + iovec_t const b = {&buf[0], 4}; + storage_error se; + writev(s, set, b, 2_piece, 0, aux::open_mode::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"))))); +} + +#if TORRENT_HAVE_MMAP +TORRENT_TEST(move_default_storage_to_self) +{ + test_move_storage_to_self(); +} + +TORRENT_TEST(move_default_storage_into_self) +{ + test_move_storage_into_self(); +} + +#endif + +TORRENT_TEST(move_posix_storage_to_self) +{ + test_move_storage_to_self(); +} + +TORRENT_TEST(move_posix_storage_into_self) +{ + test_move_storage_into_self(); +} + +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); +} + +#if TORRENT_HAVE_MMAP +TORRENT_TEST(dont_move_intermingled_files) +{ + std::string const save_path = complete("save_path_1"); + delete_dirs(combine_path(save_path, "temp_storage")); + + std::string const test_path = complete("save_path_2"); + delete_dirs(combine_path(test_path, "temp_storage")); + + aux::session_settings set; + file_storage fs; + std::vector buf; + aux::file_view_pool fp; + io_context ios; + auto s = setup_torrent(fs, fp, buf, save_path, set); + + iovec_t b = {&buf[0], 4}; + storage_error se; + s->writev(set, b, 2_piece, 0, aux::open_mode::write, disk_job_flags_t{}, 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); + + ofstream(combine_path(save_path, combine_path("temp_storage", "alien1.tmp")).c_str()); + ofstream(combine_path(save_path, combine_path("temp_storage", combine_path("folder1", "alien2.tmp"))).c_str()); + + 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"))))); +} +#endif + +namespace { + +void sync(lt::io_context& ioc, int& outstanding) +{ + while (outstanding > 0) + { + ioc.run_one(); + ioc.restart(); + } +} + +template +void test_unaligned_read(lt::disk_io_constructor_type constructor, Fun fun) +{ + lt::io_context ioc; + lt::counters cnt; + lt::settings_pack pack; + pack.set_int(lt::settings_pack::aio_threads, 1); + pack.set_int(lt::settings_pack::file_pool_size, 2); + + std::unique_ptr disk_io + = constructor(ioc, pack, cnt); + + lt::file_storage fs; + fs.add_file("test", lt::default_block_size * 2); + fs.set_num_pieces(1); + fs.set_piece_length(lt::default_block_size * 2); + + std::string const save_path = complete("save_path"); + delete_dirs(combine_path(save_path, "test")); + + lt::aux::vector prios; + lt::storage_params params(fs, nullptr + , save_path + , lt::storage_mode_sparse + , prios + , lt::sha1_hash("01234567890123456789")); + + lt::storage_holder t = disk_io->new_torrent(params, {}); + + int outstanding = 0; + lt::add_torrent_params atp; + disk_io->async_check_files(t, &atp, lt::aux::vector{} + , [&](lt::status_t, lt::storage_error const&) { --outstanding; }); + ++outstanding; + disk_io->submit_jobs(); + sync(ioc, outstanding); + + fun(disk_io.get(), t, ioc, outstanding); + + t.reset(); + disk_io->abort(true); +} + +struct write_handler +{ + write_handler(int& outstanding) : m_out(&outstanding) {} + void operator()(lt::storage_error const& ec) const + { + --(*m_out); + if (ec) std::cout << "async_write failed " << ec.ec.message() << '\n'; + TEST_CHECK(!ec); + } + int* m_out; +}; + +struct read_handler +{ + read_handler(int& outstanding, lt::span expected) : m_out(&outstanding), m_exp(expected) {} + void operator()(lt::disk_buffer_holder h, lt::storage_error const& ec) const + { + --(*m_out); + if (ec) std::cout << "async_read failed " << ec.ec.message() << '\n'; + TEST_CHECK(!ec); + TEST_CHECK(m_exp == lt::span(h.data(), h.size())); + } + int* m_out; + lt::span m_exp; +}; + +void both_sides_from_store_buffer(lt::disk_interface* disk_io, lt::storage_holder const& t, lt::io_context& ioc, int& outstanding) +{ + std::vector write_buffer(lt::default_block_size * 2); + aux::random_bytes(write_buffer); + + lt::peer_request const req0{0_piece, 0, lt::default_block_size}; + lt::peer_request const req1{0_piece, lt::default_block_size, lt::default_block_size}; + + // this is the unaligned read request + lt::peer_request const req2{0_piece, lt::default_block_size / 2, lt::default_block_size}; + + std::vector const expected_buffer(write_buffer.begin() + req2.start + , write_buffer.begin() + req2.start + req2.length); + + ++outstanding; + disk_io->async_write(t, req0, write_buffer.data(), {}, write_handler(outstanding)); + ++outstanding; + disk_io->async_write(t, req1, write_buffer.data() + lt::default_block_size, {}, write_handler(outstanding)); + ++outstanding; + disk_io->async_read(t, req2, read_handler(outstanding, expected_buffer)); + disk_io->submit_jobs(); + sync(ioc, outstanding); +} + +void first_side_from_store_buffer(lt::disk_interface* disk_io, lt::storage_holder const& t, lt::io_context& ioc, int& outstanding) +{ + std::vector write_buffer(lt::default_block_size * 2); + aux::random_bytes(write_buffer); + + lt::peer_request const req0{0_piece, 0, lt::default_block_size}; + lt::peer_request const req1{0_piece, lt::default_block_size, lt::default_block_size}; + + // this is the unaligned read request + lt::peer_request const req2{0_piece, lt::default_block_size / 2, lt::default_block_size}; + + std::vector const expected_buffer(write_buffer.begin() + req2.start + , write_buffer.begin() + req2.start + req2.length); + + ++outstanding; + disk_io->async_write(t, req0, write_buffer.data(), {}, write_handler(outstanding)); + + disk_io->submit_jobs(); + sync(ioc, outstanding); + + ++outstanding; + disk_io->async_write(t, req1, write_buffer.data() + lt::default_block_size, {}, write_handler(outstanding)); + ++outstanding; + disk_io->async_read(t, req2, read_handler(outstanding, expected_buffer)); + disk_io->submit_jobs(); + sync(ioc, outstanding); +} + +void second_side_from_store_buffer(lt::disk_interface* disk_io, lt::storage_holder const& t, lt::io_context& ioc, int& outstanding) +{ + std::vector write_buffer(lt::default_block_size * 2); + aux::random_bytes(write_buffer); + + lt::peer_request const req0{0_piece, 0, lt::default_block_size}; + lt::peer_request const req1{0_piece, lt::default_block_size, lt::default_block_size}; + + // this is the unaligned read request + lt::peer_request const req2{0_piece, lt::default_block_size / 2, lt::default_block_size}; + + std::vector const expected_buffer(write_buffer.begin() + req2.start + , write_buffer.begin() + req2.start + req2.length); + + ++outstanding; + disk_io->async_write(t, req1, write_buffer.data() + lt::default_block_size, {}, write_handler(outstanding)); + disk_io->submit_jobs(); + sync(ioc, outstanding); + + ++outstanding; + disk_io->async_write(t, req0, write_buffer.data(), {}, write_handler(outstanding)); + ++outstanding; + disk_io->async_read(t, req2, read_handler(outstanding, expected_buffer)); + disk_io->submit_jobs(); + sync(ioc, outstanding); +} + +void none_from_store_buffer(lt::disk_interface* disk_io, lt::storage_holder const& t, lt::io_context& ioc, int& outstanding) +{ + std::vector write_buffer(lt::default_block_size * 2); + aux::random_bytes(write_buffer); + + lt::peer_request const req0{0_piece, 0, lt::default_block_size}; + lt::peer_request const req1{0_piece, lt::default_block_size, lt::default_block_size}; + + // this is the unaligned read request + lt::peer_request const req2{0_piece, lt::default_block_size / 2, lt::default_block_size}; + + std::vector const expected_buffer(write_buffer.begin() + req2.start + , write_buffer.begin() + req2.start + req2.length); + + ++outstanding; + disk_io->async_write(t, req0, write_buffer.data(), {}, write_handler(outstanding)); + ++outstanding; + disk_io->async_write(t, req1, write_buffer.data() + lt::default_block_size, {}, write_handler(outstanding)); + disk_io->submit_jobs(); + sync(ioc, outstanding); + + ++outstanding; + disk_io->async_read(t, req2, read_handler(outstanding, expected_buffer)); + disk_io->submit_jobs(); + sync(ioc, outstanding); +} + +} + +#if TORRENT_HAVE_MMAP +TORRENT_TEST(mmap_unaligned_read_both_store_buffer) +{ + test_unaligned_read(lt::mmap_disk_io_constructor, both_sides_from_store_buffer); + test_unaligned_read(lt::mmap_disk_io_constructor, first_side_from_store_buffer); + test_unaligned_read(lt::mmap_disk_io_constructor, second_side_from_store_buffer); + test_unaligned_read(lt::mmap_disk_io_constructor, none_from_store_buffer); +} +#endif + +TORRENT_TEST(posix_unaligned_read_both_store_buffer) +{ + test_unaligned_read(lt::posix_disk_io_constructor, both_sides_from_store_buffer); + test_unaligned_read(lt::posix_disk_io_constructor, first_side_from_store_buffer); + test_unaligned_read(lt::posix_disk_io_constructor, second_side_from_store_buffer); + test_unaligned_read(lt::posix_disk_io_constructor, none_from_store_buffer); +} diff --git a/test/test_store_buffer.cpp b/test/test_store_buffer.cpp new file mode 100644 index 0000000..04ff414 --- /dev/null +++ b/test/test_store_buffer.cpp @@ -0,0 +1,186 @@ +/* + +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 "test.hpp" +#include "libtorrent/aux_/store_buffer.hpp" +#include "libtorrent/disk_interface.hpp" // for default_block_size + +using lt::aux::torrent_location; +using lt::aux::store_buffer; + +namespace { + + char buf1; + char buf2; + char buf3; + char buf4; + + lt::storage_index_t const st0(0); + lt::storage_index_t const st1(1); + lt::piece_index_t const p0(0); + lt::piece_index_t const p1(1); + +std::vector build_locations() +{ + std::vector ret; + for (auto s : {st0, st1}) + { + for (auto p : {p0, p1}) + { + for (auto o : {0, lt::default_block_size}) + { + ret.emplace_back(s, p, o); + } + } + } + return ret; +} + +void check(store_buffer const& sb, torrent_location l, char const* expected) +{ + bool called = false; + bool const ret = sb.get(l, [&](char const* buf) { + TEST_EQUAL(buf, expected); + called = true; + }); + TEST_EQUAL(called, true); + TEST_EQUAL(ret, true); +} + +void check_miss(store_buffer const& sb, torrent_location l) +{ + int const ret = sb.get(l, [&](char const* b) { + TEST_ERROR(b); + return 1337; + }); + TEST_EQUAL(ret, 0); +} + +void check2(store_buffer const& sb, torrent_location l0, torrent_location l1 + , char const* expected0, char const* expected1) +{ + bool called = false; + int const ret = sb.get2(l0, l1, [&](char const* b0, char const* b1) { + TEST_EQUAL(b0, expected0); + TEST_EQUAL(b1, expected1); + called = true; + return 1337; + }); + TEST_EQUAL(called, true); + TEST_EQUAL(ret, 1337); +} + +void check2_miss(store_buffer const& sb, torrent_location l0, torrent_location l1) +{ + int const ret = sb.get2(l0, l1, [&](char const* b0, char const* b1) { + TEST_ERROR(b0); + TEST_ERROR(b1); + return 1337; + }); + TEST_EQUAL(ret, 0); +} + +} + +TORRENT_TEST(store_buffer_unique_keys) +{ + auto const locations = build_locations(); + store_buffer sb; + // ensure all locations are independent + for (auto l1 : locations) + { + sb.insert(l1, &buf1); + for (auto l2 : locations) + { + if (l1 == l2) + check(sb, l1, &buf1); + else + check_miss(sb, l2); + } + sb.erase(l1); + } +} + +TORRENT_TEST(store_buffer_get) +{ + auto const loc = build_locations(); + store_buffer sb; + sb.insert(loc[0], &buf1); + sb.insert(loc[1], &buf2); + sb.insert(loc[2], &buf3); + sb.insert(loc[3], &buf4); + + check(sb, loc[0], &buf1); + check(sb, loc[1], &buf2); + check(sb, loc[2], &buf3); + check(sb, loc[3], &buf4); + + check_miss(sb, loc[4]); + check_miss(sb, loc[5]); + check_miss(sb, loc[6]); + check_miss(sb, loc[7]); +} + +TORRENT_TEST(store_buffer_get2) +{ + auto const loc = build_locations(); + store_buffer sb; + sb.insert(loc[0], &buf1); + sb.insert(loc[1], &buf2); + sb.insert(loc[2], &buf3); + sb.insert(loc[3], &buf4); + + // left side + check2(sb, loc[0], loc[4], &buf1, nullptr); + check2(sb, loc[1], loc[5], &buf2, nullptr); + check2(sb, loc[2], loc[6], &buf3, nullptr); + check2(sb, loc[3], loc[7], &buf4, nullptr); + + // right side + check2(sb, loc[4], loc[0], nullptr, &buf1); + check2(sb, loc[5], loc[1], nullptr, &buf2); + check2(sb, loc[6], loc[2], nullptr, &buf3); + check2(sb, loc[7], loc[3], nullptr, &buf4); + + // both sides + check2(sb, loc[3], loc[0], &buf4, &buf1); + check2(sb, loc[2], loc[1], &buf3, &buf2); + check2(sb, loc[1], loc[2], &buf2, &buf3); + check2(sb, loc[0], loc[3], &buf1, &buf4); + + // neither side + check2_miss(sb, loc[4], loc[7]); + check2_miss(sb, loc[5], loc[6]); + check2_miss(sb, loc[6], loc[5]); + check2_miss(sb, loc[7], loc[4]); +} + diff --git a/test/test_string.cpp b/test/test_string.cpp new file mode 100644 index 0000000..7bdb4d6 --- /dev/null +++ b/test/test_string.cpp @@ -0,0 +1,566 @@ +/* + +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2016, Steven Siloti +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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"); +} + +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 + TORRENT_UNUSED(output); + 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_quotes) +{ + TEST_CHECK(split_string_quotes("a b"_sv, ' ') == std::make_pair("a"_sv, "b"_sv)); + TEST_CHECK(split_string_quotes("\"a b\" c"_sv, ' ') == std::make_pair("\"a b\""_sv, "c"_sv)); + TEST_CHECK(split_string_quotes("\"a b\"foobar c"_sv, ' ') == std::make_pair("\"a b\"foobar"_sv, "c"_sv)); + TEST_CHECK(split_string_quotes("a\nb foobar"_sv, ' ') == std::make_pair("a\nb"_sv, "foobar"_sv)); + TEST_CHECK(split_string_quotes("a b\"foo\"bar"_sv, '"') == std::make_pair("a b"_sv, "foo\"bar"_sv)); + TEST_CHECK(split_string_quotes("a"_sv, ' ') == std::make_pair("a"_sv, ""_sv)); + TEST_CHECK(split_string_quotes("\"a b"_sv, ' ') == std::make_pair("\"a b"_sv, ""_sv)); + TEST_CHECK(split_string_quotes(""_sv, ' ') == std::make_pair(""_sv, ""_sv)); +} + +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"_sv, "b\" c"_sv)); + TEST_CHECK(split_string("\"a b\"foobar c"_sv, ' ') == std::make_pair("\"a"_sv, "b\"foobar 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"_sv, "b"_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..e726da0 --- /dev/null +++ b/test/test_tailqueue.cpp @@ -0,0 +1,170 @@ +/* + +Copyright (c) 2014-2017, 2019, Arvid Norberg +Copyright (c) 2018, 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/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..efc1b6c --- /dev/null +++ b/test/test_threads.cpp @@ -0,0 +1,129 @@ +/* + +Copyright (c) 2010, 2013, 2015-2017, 2019, Arvid Norberg +Copyright (c) 2018, 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 "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..ee5f38f --- /dev/null +++ b/test/test_time.cpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2013, 2015-2017, 2019, Arvid Norberg +Copyright (c) 2018, 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/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..f50e045 --- /dev/null +++ b/test/test_time_critical.cpp @@ -0,0 +1,61 @@ +/* + +Copyright (c) 2014-2015, 2017, 2019-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 "swarm_suite.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" +#include "test_utils.hpp" +#include "libtorrent/download_priority.hpp" +#include "libtorrent/torrent_info.hpp" + +TORRENT_TEST(time_crititcal) +{ + test_swarm(test_flags::time_critical); +} + +TORRENT_TEST(time_crititcal_zero_prio) +{ + auto ti = generate_torrent(); + + lt::session ses(settings()); + + lt::add_torrent_params atp; + atp.ti = ti; + atp.piece_priorities.resize(std::size_t(ti->num_pieces()), lt::dont_download); + atp.save_path = "."; + auto h = ses.add_torrent(atp); + + wait_for_downloading(ses, ""); + + h.set_piece_deadline(0_piece, 0, lt::torrent_handle::alert_when_available); +} + diff --git a/test/test_timestamp_history.cpp b/test/test_timestamp_history.cpp new file mode 100644 index 0000000..6ab4320 --- /dev/null +++ b/test/test_timestamp_history.cpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2015, 2017, 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 "test.hpp" +#include "libtorrent/aux_/timestamp_history.hpp" + +TORRENT_TEST(timestamp_history) +{ + using namespace lt; + + aux::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..8dd4e02 --- /dev/null +++ b/test/test_torrent.cpp @@ -0,0 +1,896 @@ +/* + +Copyright (c) 2008-2009, 2013-2020, Arvid Norberg +Copyright (c) 2016, 2018-2019, Alden Torres +Copyright (c) 2017, Falcosc +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, d-komarov +Copyright (c) 2020, AllSeeingEyeTolledEweSew +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 "libtorrent/span.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/random.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, test_listen_interface()); + 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[0_file] = 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(0_piece, 1_pri); + st = h.status(); + TEST_CHECK(st.pieces.size() > 0 && st.pieces[0_piece] == 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(0_piece, &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(0_piece); + 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(0_piece))) == 0); + TEST_CHECK(rpa->size == info->piece_size(0_piece)); + TEST_CHECK(rpa->piece == 0_piece); + TEST_CHECK(hasher(piece).final() == info->hash_for_piece(0_piece)); + } + } + + 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(65535 * 16 * 1024); +} + +TORRENT_TEST(total_wanted) +{ + file_storage fs; + + fs.add_file("test_torrent_dir4/tmp1", default_block_size); + fs.add_file("test_torrent_dir4/tmp2", default_block_size); + fs.add_file("test_torrent_dir4/tmp3", default_block_size); + fs.add_file("test_torrent_dir4/tmp4", default_block_size); + + lt::create_torrent t(fs, default_block_size); + t.set_hash(0_piece, sha1_hash::max()); + t.set_hash(1_piece, sha1_hash::max()); + t.set_hash(2_piece, sha1_hash::max()); + t.set_hash(3_piece, sha1_hash::max()); + + 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, test_listen_interface()); + 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(8, 0_pri); + p.file_priorities[2] = 1_pri; + + torrent_handle h = ses.add_torrent(std::move(p)); + + torrent_status st = h.status(); + TEST_EQUAL(st.total_wanted, default_block_size); + 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{2}, default_priority); + h.file_priority(file_index_t{2}, dont_download); + TEST_CHECK(wait_priority(h, aux::vector(static_cast(fs.num_files()), dont_download))); + 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); + t.set_hash(0_piece, sha1_hash::max()); + 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, test_listen_interface()); + 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_hashes = info_hash_t{}; +#if TORRENT_ABI_VERSION < 3 + p.info_hash = sha1_hash{}; +#endif + 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); + t.set_hash(0_piece, sha1_hash::max()); + 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_hashes = lt::info_hash_t(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); + t.set_hash(0_piece, sha1_hash::max()); + 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()); + 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); + t.set_hash(0_piece, sha1_hash::max()); + 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()); + 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_hashes = lt::info_hash_t(lt::sha1_hash("01010101010101010101")); + p.piece_priorities.resize(9999, lt::low_priority); + p.save_path = "."; + + lt::session ses(settings()); + 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; + bencode(std::back_inserter(tmp), 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", default_block_size); + lt::create_torrent t(fs, default_block_size); + + std::vector piece(default_block_size); + 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); + + t.set_hash2(file_index_t{ 0 }, piece_index_t::diff_type{ 0 }, lt::hasher256(piece).final()); + + std::vector tmp; + bencode(std::back_inserter(tmp), t.generate()); + auto info = std::make_shared(tmp, from_span); + test_running_torrent(info, default_block_size); + } +} + +#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&, client_data_t) + { + ++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); + + 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; + bencode(std::back_inserter(tmp), 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, lt::create_torrent::v1_only); + t.set_hash(0_piece, sha1_hash::max()); + + std::vector tmp; + bencode(std::back_inserter(tmp), t.generate()); + auto info = std::make_shared(tmp, from_span); + + TEST_EQUAL(info->files().file_path(0_file), combine_path("test3","tmp1")); + + // move "test3/tmp1" -> "tmp1" + info->rename_file(0_file, "tmp1"); + + TEST_EQUAL(info->files().file_path(0_file), "tmp1"); +} + +TORRENT_TEST(torrent_status) +{ + TEST_EQUAL(static_cast(torrent_status::error_file_none), -1); + TEST_EQUAL(static_cast(torrent_status::error_file_metadata), -4); + 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 const& atp) +{ + 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); + t.set_hash(0_piece, sha1_hash::max()); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + auto ti = std::make_shared(buf, from_span); + add_torrent_params p = atp; + 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(0_piece), false); + TEST_EQUAL(h.have_piece(100_piece), 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; + lt::file_storage fs; + fs.set_piece_length(piece_size); + create_random_files(".", file_sizes, &fs); + p.ti = make_torrent(fs); + + p.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(0_piece), true); + TEST_EQUAL(h.have_piece(100_piece), 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(aux::calc_num_pieces(fs)); + 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(), 0x4000, 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(), 0x8000, 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(lt::settings_pack::alert_mask, lt::alert_category::status | lt::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_EQUAL(aux::get_symlink_path(f), "Versions/A/SDL2"); +} +#endif + +TORRENT_TEST(redundant_add_piece) +{ + file_storage fs; + fs.add_file("tmp1", 128 * 1024 * 8); + lt::create_torrent t(fs, 128 * 1024); + + TEST_CHECK(t.num_pieces() > 0); + + std::vector piece_data(std::size_t(fs.piece_length()), 0); + aux::random_bytes(piece_data); + + sha1_hash const ph = lt::hasher(piece_data).final(); + for (auto const i : fs.piece_range()) + t.set_hash(i, ph); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + auto ti = std::make_shared(buf, from_span); + + lt::session ses(settings()); + lt::add_torrent_params atp; + atp.ti = ti; + atp.flags &= ~torrent_flags::paused; + atp.save_path = "."; + auto h = ses.add_torrent(atp); + wait_for_downloading(ses, ""); + + h.add_piece(0_piece, piece_data.data()); + h.set_piece_deadline(0_piece, 0, torrent_handle::alert_when_available); + h.prioritize_pieces(std::vector(std::size_t(ti->num_pieces()), lt::dont_download)); + h.add_piece(0_piece, piece_data.data()); + std::this_thread::sleep_for(lt::seconds(2)); +} + +TORRENT_TEST(test_in_session) +{ + 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); + TEST_CHECK(h.in_session()); + ses.remove_torrent(h); + TEST_CHECK(!h.in_session()); +} diff --git a/test/test_torrent_info.cpp b/test/test_torrent_info.cpp new file mode 100644 index 0000000..8502169 --- /dev/null +++ b/test/test_torrent_info.cpp @@ -0,0 +1,1362 @@ +/* + +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2017-2019, Steven Siloti +Copyright (c) 2019, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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" // for load_file +#include "test_utils.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/disk_interface.hpp" // for default_block_size +#include "libtorrent/aux_/escape_string.hpp" // for convert_path_to_posix +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/hex.hpp" // to_hex +#include "libtorrent/write_resume_data.hpp" // write_torrent_file + +#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 + for (auto const i : fs.piece_range()) + t.set_hash(i, sha1_hash::max()); + + t.add_collection("collection1"); + t.add_collection("collection2"); + + t.add_similar_torrent(sha1_hash("abababababababababab")); + t.add_similar_torrent(sha1_hash("babababababababababa")); + + entry tor = t.generate(); + std::vector tmp; + bencode(std::back_inserter(tmp), 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 +{ + test_torrent_t(char const* f, std::function t = {}) // NOLINT + : file(f), test(std::move(t)) {} + + char const* file; + std::function test; +}; + +using namespace lt; + +#ifdef TORRENT_WINDOWS +#define SEPARATOR "\\" +#else +#define SEPARATOR "/" +#endif + +static test_torrent_t const test_torrents[] = +{ + { "base.torrent"}, + { "empty_path.torrent" }, + { "parent_path.torrent" }, + { "hidden_parent_path.torrent" }, + { "single_multi_file.torrent" }, + { "slash_path.torrent", [](torrent_info const* ti) { + TEST_EQUAL(ti->num_files(), 1); + TEST_EQUAL(ti->files().file_path(file_index_t{0}), "temp" SEPARATOR "bar"); + } + }, + { "slash_path2.torrent", [](torrent_info const* ti) { + TEST_EQUAL(ti->num_files(), 1); + TEST_EQUAL(ti->files().file_path(file_index_t{0}), "temp" SEPARATOR "abc....def" SEPARATOR "bar"); + } + }, + { "slash_path3.torrent", [](torrent_info const* ti) { + TEST_EQUAL(ti->num_files(), 1); + TEST_EQUAL(ti->files().file_path(file_index_t{0}), "temp....abc"); + } + }, + { "backslash_path.torrent" }, + { "url_list.torrent" }, + { "url_list2.torrent" }, + { "url_list3.torrent" }, + { "httpseed.torrent" }, + { "empty_httpseed.torrent" }, + { "long_name.torrent" }, + { "whitespace_url.torrent", [](torrent_info const* ti) { + // 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"); + } + }, + { "duplicate_files.torrent", [](torrent_info const* ti) { + // 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")); + } + }, + { "pad_file.torrent", [](torrent_info const* ti) { + 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); + } + }, + { "creation_date.torrent", [](torrent_info const* ti) { + TEST_EQUAL(ti->creation_date(), 1234567); + } + }, + { "no_creation_date.torrent", [](torrent_info const* ti) { + TEST_CHECK(!ti->creation_date()); + } + }, + { "url_seed.torrent", [](torrent_info const* ti) { + 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 + } + }, + { "url_seed_multi.torrent", [](torrent_info const* ti) { + 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 + } + }, + { "url_seed_multi_single_file.torrent", [](torrent_info const* ti) { + TEST_EQUAL(ti->web_seeds().size(), 1); + TEST_EQUAL(ti->web_seeds()[0].url, "http://test.com/file/temp/foo/bar.txt"); + } + }, + { "url_seed_multi_space.torrent", [](torrent_info const* ti) { + 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 + } + }, + { "url_seed_multi_space_nolist.torrent", [](torrent_info const* ti) { + 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 + } + }, + { "empty_path_multi.torrent" }, + { "duplicate_web_seeds.torrent", [](torrent_info const* ti) { + TEST_EQUAL(ti->web_seeds().size(), 3); + } + }, + { "invalid_name2.torrent", [](torrent_info const* ti) { + // 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"); + } + }, + { "invalid_name3.torrent", [](torrent_info const* ti) { + // windows does not allow trailing spaces in filenames +#ifdef TORRENT_WINDOWS + TEST_EQUAL(ti->name(), "foobar"); +#else + TEST_EQUAL(ti->name(), "foobar "); +#endif + } + }, + { "symlink1.torrent", [](torrent_info const* ti) { + TEST_EQUAL(ti->num_files(), 2); + TEST_EQUAL(ti->files().symlink(file_index_t{1}), "temp" SEPARATOR "a" SEPARATOR "b" SEPARATOR "bar"); + } + }, + { "symlink2.torrent", [](torrent_info const* ti) { + 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"); + } + }, + { "unordered.torrent" }, + { "symlink_zero_size.torrent", [](torrent_info const* ti) { + TEST_EQUAL(ti->num_files(), 2); + TEST_EQUAL(ti->files().symlink(1_file), "temp" SEPARATOR "a" SEPARATOR "b" SEPARATOR "bar"); + } + }, + { "pad_file_no_path.torrent", [](torrent_info const* ti) { + TEST_EQUAL(ti->num_files(), 2); + TEST_EQUAL(ti->files().file_path(file_index_t{1}), combine_path(".pad", "2124")); + } + }, + { "large.torrent" }, + { "absolute_filename.torrent", [](torrent_info const* ti) { + 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")); + } + }, + { "invalid_filename.torrent", [](torrent_info const* ti) { + TEST_EQUAL(ti->num_files(), 2); + } + }, + { "invalid_filename2.torrent", [](torrent_info const* ti) { + TEST_EQUAL(ti->num_files(), 3); + } + }, + { "overlapping_symlinks.torrent", [](torrent_info const* ti) { + 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"); + } + }, + { "v2.torrent", [](torrent_info const* ti) { + TEST_EQUAL(ti->num_files(), 1); + TEST_EQUAL(ti->files().file_path(file_index_t{ 0 }), "test64K"_sv); + TEST_EQUAL(ti->files().file_size(file_index_t{ 0 }), 65536); + TEST_EQUAL(aux::to_hex(ti->files().root(file_index_t{ 0 })), "60aae9c7b428f87e0713e88229e18f0adf12cd7b22a0dd8a92bb2485eb7af242"_sv); + TEST_EQUAL(ti->info_hashes().has_v1(), true); + TEST_EQUAL(ti->info_hashes().has_v2(), true); + TEST_EQUAL(aux::to_hex(ti->info_hashes().v2), "597b180c1a170a585dfc5e85d834d69013ceda174b8f357d5bb1a0ca509faf0a"_sv); + TEST_CHECK(ti->v2()); + TEST_CHECK(ti->v1()); + } + }, + { "v2_multipiece_file.torrent", [](torrent_info const* ti) { + TEST_EQUAL(ti->num_files(), 1); + TEST_EQUAL(ti->files().file_path(file_index_t{ 0 }), "test1MB"_sv); + TEST_EQUAL(ti->files().file_size(file_index_t{ 0 }), 1048576); + TEST_EQUAL(aux::to_hex(ti->files().root(file_index_t{ 0 })), "515ea9181744b817744ded9d2e8e9dc6a8450c0b0c52e24b5077f302ffbd9008"_sv); + TEST_EQUAL(ti->info_hashes().has_v1(), true); + TEST_EQUAL(ti->info_hashes().has_v2(), true); + TEST_EQUAL(aux::to_hex(ti->info_hashes().v2), "108ac2c3718ce722e6896edc56c4afa98f1d711ecaace7aad74fca418ebd03de"_sv); + } + }, + { "v2_only.torrent", [](torrent_info const* ti) { + TEST_EQUAL(ti->num_files(), 1); + TEST_EQUAL(ti->files().file_path(file_index_t{ 0 }), "test1MB"_sv); + TEST_EQUAL(ti->files().file_size(file_index_t{ 0 }), 1048576); + TEST_EQUAL(aux::to_hex(ti->files().root(file_index_t{ 0 })), "515ea9181744b817744ded9d2e8e9dc6a8450c0b0c52e24b5077f302ffbd9008"_sv); + TEST_EQUAL(ti->info_hashes().has_v1(), false); + TEST_EQUAL(ti->info_hashes().has_v2(), true); + TEST_EQUAL(aux::to_hex(ti->info_hashes().v2), "95e04d0c4bad94ab206efa884666fd89777dbe4f7bd9945af1829037a85c6192"_sv); + TEST_CHECK(ti->v2()); + TEST_CHECK(!ti->v1()); + } + }, + { "v2_invalid_filename.torrent", [](torrent_info const* ti) { + TEST_EQUAL(ti->num_files(), 1); + TEST_EQUAL(ti->files().file_path(file_index_t{0}), "_estMB"_sv); + } + }, + { "v2_multiple_files.torrent", [](torrent_info* ti) { + TEST_EQUAL(ti->v2_piece_hashes_verified(), true); + TEST_EQUAL(ti->num_files(), 5); + TEST_CHECK(ti->v2()); + ti->free_piece_layers(); + TEST_CHECK(ti->v2()); + TEST_EQUAL(ti->v2_piece_hashes_verified(), false); + } + }, + { "v2_symlinks.torrent", [](torrent_info const* ti) { + TEST_CHECK(ti->num_files() > 3); + TEST_EQUAL(ti->files().symlink(0_file), "SDL2.framework" SEPARATOR "Versions" SEPARATOR "Current" SEPARATOR "Headers"); + TEST_EQUAL(ti->files().symlink(1_file), "SDL2.framework" SEPARATOR "Versions" SEPARATOR "Current" SEPARATOR "Resources"); + TEST_EQUAL(ti->files().symlink(2_file), "SDL2.framework" SEPARATOR "Versions" SEPARATOR "Current" SEPARATOR "SDL2"); + } + }, + { "v2_hybrid.torrent", [](torrent_info const* ti) { + TEST_CHECK(ti->info_hashes().has_v1()); + TEST_CHECK(ti->info_hashes().has_v2()); + } + }, + { "empty-files-1.torrent", [](torrent_info const* ti) { + TEST_CHECK(ti->info_hashes().has_v1()); + TEST_CHECK(ti->info_hashes().has_v2()); + } + }, + { "empty-files-2.torrent", [](torrent_info const* ti) { + TEST_CHECK(ti->info_hashes().has_v1()); + TEST_CHECK(ti->info_hashes().has_v2()); + } + }, + { "empty-files-3.torrent", [](torrent_info const* ti) { + TEST_CHECK(ti->info_hashes().has_v1()); + TEST_CHECK(ti->info_hashes().has_v2()); + } + }, + { "empty-files-4.torrent", [](torrent_info const* ti) { + TEST_CHECK(ti->info_hashes().has_v1()); + TEST_CHECK(ti->info_hashes().has_v2()); + } + }, + { "empty-files-5.torrent", [](torrent_info const* ti) { + TEST_CHECK(ti->info_hashes().has_v1()); + TEST_CHECK(ti->info_hashes().has_v2()); + } + }, + { "v2_no_piece_layers.torrent", [](torrent_info const* ti) { + // it's OK to not have a piece layers field. + // It's just like adding a magnet link + TEST_CHECK(!ti->info_hashes().has_v1()); + TEST_CHECK(ti->info_hashes().has_v2()); + } + }, + { "v2_incomplete_piece_layer.torrent", [](torrent_info const* ti) { + // it's OK for some files to not have a piece layer. + // It's just like adding a magnet link + TEST_CHECK(!ti->info_hashes().has_v1()); + TEST_CHECK(ti->info_hashes().has_v2()); + } + }, +}; + +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_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}, + { "zero.torrent", errors::torrent_invalid_length}, + { "zero2.torrent", errors::torrent_invalid_length}, + { "v2_mismatching_metadata.torrent", errors::torrent_inconsistent_files}, + { "v2_no_power2_piece.torrent", errors::torrent_missing_piece_length}, + { "v2_invalid_file.torrent", errors::torrent_file_parse_failed}, + { "v2_deep_recursion.torrent", errors::torrent_file_parse_failed}, + { "v2_non_multiple_piece_layer.torrent", errors::torrent_invalid_piece_layer}, + { "v2_piece_layer_invalid_file_hash.torrent", errors::torrent_invalid_piece_layer}, + { "v2_invalid_piece_layer.torrent", errors::torrent_invalid_piece_layer}, + { "v2_invalid_piece_layer_size.torrent", errors::torrent_invalid_piece_layer}, + { "v2_bad_file_alignment.torrent", errors::torrent_inconsistent_files}, + { "v2_unordered_files.torrent", errors::invalid_bencoding}, + { "v2_overlong_integer.torrent", errors::invalid_bencoding}, + { "v2_missing_file_root_invalid_symlink.torrent", errors::torrent_missing_pieces_root}, + { "v2_large_file.torrent", errors::torrent_invalid_length}, + { "v2_large_offset.torrent", errors::too_many_pieces_in_torrent}, + { "v2_piece_size.torrent", errors::torrent_missing_piece_length}, + { "v2_invalid_pad_file.torrent", errors::torrent_invalid_pad_file}, + { "v2_zero_root.torrent", errors::torrent_missing_pieces_root}, + { "v2_zero_root_small.torrent", errors::torrent_missing_pieces_root}, +}; + +} // anonymous namespace + +// TODO: test remap_files +// 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: 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: 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 constructor that takes an invalid bencoded buffer +// TODO: verify_encoding with a string that triggers character replacement + +TORRENT_TEST(add_tracker) +{ + torrent_info ti(info_hash_t(sha1_hash(" "))); + TEST_EQUAL(ti.trackers().size(), 0); + + ti.add_tracker("http://test.com/announce"); + TEST_EQUAL(ti.trackers().size(), 1); + + announce_entry ae = ti.trackers()[0]; + TEST_EQUAL(ae.url, "http://test.com/announce"); + + ti.clear_trackers(); + TEST_EQUAL(ti.trackers().size(), 0); +} + +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(info_hash_t(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(info_hash_t(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(info_hash_t(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); +} + +TORRENT_TEST(sanitize_path_truncate) +{ + using lt::aux::sanitize_append_path_element; + + 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) +{ + using lt::aux::sanitize_append_path_element; + + 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; + using lt::aux::sanitize_append_path_element; + 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) +{ + using lt::aux::sanitize_append_path_element; + 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) +{ + using lt::aux::sanitize_append_path_element; + 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) +{ + using lt::aux::sanitize_append_path_element; + 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) +{ + using lt::aux::sanitize_append_path_element; + 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) +{ + using aux::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()); + + // construct a piece_picker to get some more test coverage. Perhaps + // loading the torrent is fine, but if we can't construct a piece_picker + // for it, it's still no good. + piece_picker pp(ti->total_size(), ti->piece_length()); + + TEST_CHECK(ti->piece_length() < std::numeric_limits::max() / 2); + TEST_EQUAL(ti->v1(), ti->info_hashes().has_v1()); + TEST_EQUAL(ti->v2(), ti->info_hashes().has_v2()); + + if (t.test) t.test(ti.get()); + + file_storage const& fs = ti->files(); + for (file_index_t const idx : fs.file_range()) + { + 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() : ""); + } + } +} + +TORRENT_TEST(parse_invalid_torrents) +{ + std::string const root_dir = parent_path(current_working_directory()); + for (auto const& e : test_error_torrents) + { + error_code ec; + std::printf("loading %s\n", e.file); + std::vector data; + std::string const filename = combine_path(combine_path(root_dir, "test_torrents") + , e.file); + TEST_CHECK(load_file(filename, data, ec) == 0); + TEST_CHECK(!ec); + + auto ti = std::make_shared(bdecode(data, 1000), ec); + std::printf("E: \"%s\"\nexpected: \"%s\"\n", ec.message().c_str() + , e.error.message().c_str()); + TEST_EQUAL(ec.message(), e.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); + + // This test creates torrents with duplicate (identical) filenames, which + // isn't supported by v2 torrents, so we can only test this with v1 torrents + lt::create_torrent t(fs, 0x4000, create_torrent::v1_only); + + for (auto const i : fs.piece_range()) + t.set_hash(i, sha1_hash::max()); + + 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); + 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); +} + +TORRENT_TEST(torrent_info_with_hashes_roundtrip) +{ + std::string const root_dir = parent_path(current_working_directory()); + std::string const filename = combine_path(combine_path(root_dir, "test_torrents"), "v2_only.torrent"); + + error_code ec; + std::vector data; + TEST_CHECK(load_file(filename, data, ec) == 0); + + auto ti = std::make_shared(data, ec, from_span); + TEST_CHECK(!ec); + if (ec) std::printf(" loading(\"%s\") -> failed %s\n", filename.c_str() + , ec.message().c_str()); + + TEST_CHECK(ti->v2()); + TEST_CHECK(!ti->v1()); + TEST_EQUAL(ti->v2_piece_hashes_verified(), true); + + add_torrent_params atp; + atp.ti = ti; + atp.save_path = "."; + + session ses; + torrent_handle h = ses.add_torrent(atp); + + TEST_CHECK(ti->v2()); + TEST_CHECK(!ti->v1()); + + { + auto ti2 = h.torrent_file(); + TEST_CHECK(ti2->v2()); + TEST_CHECK(!ti2->v1()); + TEST_EQUAL(ti2->v2_piece_hashes_verified(), false); + } + + ti = h.torrent_file_with_hashes(); + + TEST_CHECK(ti->v2()); + TEST_CHECK(!ti->v1()); + TEST_EQUAL(ti->v2_piece_hashes_verified(), true); + + std::vector out_buffer = serialize(*ti); + + TEST_EQUAL(out_buffer, data); +} + +TORRENT_TEST(write_torrent_file_roundtrip) +{ + std::string const root_dir = parent_path(current_working_directory()); + std::string const filename = combine_path(combine_path(root_dir, "test_torrents"), "v2_only.torrent"); + + error_code ec; + std::vector data; + TEST_CHECK(load_file(filename, data, ec) == 0); + + auto ti = std::make_shared(data, ec, from_span); + TEST_CHECK(!ec); + if (ec) std::printf(" loading(\"%s\") -> failed %s\n", filename.c_str() + , ec.message().c_str()); + + TEST_CHECK(ti->v2()); + TEST_CHECK(!ti->v1()); + TEST_EQUAL(ti->v2_piece_hashes_verified(), true); + + add_torrent_params atp; + atp.ti = ti; + atp.save_path = "."; + + session ses; + torrent_handle h = ses.add_torrent(atp); + + h.save_resume_data(torrent_handle::save_info_dict); + alert const* a = wait_for_alert(ses, save_resume_data_alert::alert_type); + + TORRENT_ASSERT(a); + entry e = write_torrent_file(static_cast(a)->params); + std::vector out_buffer; + bencode(std::back_inserter(out_buffer), e); + + TEST_EQUAL(out_buffer, data); +} diff --git a/test/test_torrent_list.cpp b/test/test_torrent_list.cpp new file mode 100644 index 0000000..dc25578 --- /dev/null +++ b/test/test_torrent_list.cpp @@ -0,0 +1,249 @@ +/* + +Copyright (c) 2018, Steven Siloti +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 "test.hpp" +#include "libtorrent/aux_/torrent_list.hpp" +#include "libtorrent/sha1_hash.hpp" + +using namespace lt; + +namespace { + +sha1_hash const sha1_1("abababababababababab"); +sha1_hash const sha1_2("cbcbcbcbcbcbcbcbcbcb"); +sha1_hash const sha1_3("cdcdcdcdcdcdcdcdcdcd"); +sha1_hash const sha1_4("edededededededededed"); +sha256_hash const sha2_1("xbxbxbxbxbxbxbxbxbxbxbxbxbxbxbxb"); +sha1_hash const sha2_1_truncated("xbxbxbxbxbxbxbxbxbxb"); + +info_hash_t const v1(sha1_1); +info_hash_t const v2(sha2_1); +info_hash_t const hybrid(sha1_1, sha2_1); + +using ih = lt::info_hash_t; +} + +TORRENT_TEST(torrent_list_empty) +{ + aux::torrent_list l; + TEST_CHECK(l.empty()); + TEST_CHECK(l.begin() == l.end()); + l.insert(v1, std::make_shared(1337)); + TEST_CHECK(!l.empty()); + TEST_CHECK(l.begin() != l.end()); +} + +TORRENT_TEST(torrent_list_size) +{ + aux::torrent_list l; + TEST_EQUAL(l.size(), 0); + l.insert(ih(sha1_1), std::make_shared(1337)); + TEST_EQUAL(l.size(), 1); + l.insert(ih(sha1_2), std::make_shared(1338)); + TEST_EQUAL(l.size(), 2); + l.insert(ih(sha1_3), std::make_shared(1339)); + TEST_EQUAL(l.size(), 3); + + TEST_EQUAL(*l.find(sha1_1), 1337); + TEST_EQUAL(*l.find(sha1_2), 1338); + TEST_EQUAL(*l.find(sha1_3), 1339); +} + +TORRENT_TEST(torrent_list_duplicates) +{ + aux::torrent_list l; + TEST_EQUAL(l.size(), 0); + TEST_CHECK(l.insert(v1, std::make_shared(1337))); + TEST_EQUAL(l.size(), 1); + TEST_CHECK(!l.insert(v1, std::make_shared(1338))); + TEST_EQUAL(l.size(), 1); + TEST_EQUAL(*l.find(sha1_1), 1337); +} + +TORRENT_TEST(torrent_list_duplicates_v1) +{ + aux::torrent_list l; + TEST_EQUAL(l.size(), 0); + TEST_CHECK(l.insert(hybrid, std::make_shared(1337))); + TEST_EQUAL(l.size(), 1); + TEST_CHECK(!l.insert(v1, std::make_shared(1338))); + TEST_EQUAL(l.size(), 1); + TEST_EQUAL(*l.find(sha1_1), 1337); + TEST_EQUAL(*l.find(sha2_1_truncated), 1337); +} + +TORRENT_TEST(torrent_list_duplicates_v2) +{ + aux::torrent_list l; + TEST_EQUAL(l.size(), 0); + TEST_CHECK(l.insert(hybrid, std::make_shared(1337))); + TEST_EQUAL(l.size(), 1); + TEST_CHECK(!l.insert(v2, std::make_shared(1338))); + TEST_EQUAL(l.size(), 1); + TEST_EQUAL(*l.find(sha1_1), 1337); + TEST_EQUAL(*l.find(sha2_1_truncated), 1337); +} + +TORRENT_TEST(torrent_list_duplicates_self) +{ + aux::torrent_list l; + TEST_EQUAL(l.size(), 0); + TEST_CHECK(l.insert(ih(sha2_1_truncated, sha2_1), std::make_shared(1337))); + TEST_EQUAL(l.size(), 1); + TEST_CHECK(*l.find(sha2_1_truncated) == 1337); + + TEST_CHECK(l.erase(ih(sha2_1_truncated, sha2_1))); + TEST_EQUAL(l.size(), 0); + TEST_CHECK(l.find(sha2_1_truncated) == nullptr); +} + +TORRENT_TEST(torrent_truncated_list_lookup) +{ + aux::torrent_list l; + l.insert(v2, std::make_shared(1337)); + l.insert(v1, std::make_shared(1338)); + + TEST_EQUAL(*l.find(sha2_1_truncated), 1337); + TEST_EQUAL(*l.find(sha1_1), 1338); + TEST_CHECK(l.find(sha1_3) == nullptr); +} + +TORRENT_TEST(torrent_list_lookup) +{ + aux::torrent_list l; + l.insert(ih(sha1_1), std::make_shared(1337)); + l.insert(ih(sha1_2), std::make_shared(1338)); + + TEST_EQUAL(*l.find(sha1_1), 1337); + TEST_EQUAL(*l.find(sha1_2), 1338); + TEST_CHECK(l.find(sha1_3) == nullptr); +} + +TORRENT_TEST(torrent_list_order) +{ + aux::torrent_list l; + l.insert(ih(sha1_1), std::make_shared(1)); + l.insert(ih(sha1_2), std::make_shared(2)); + l.insert(ih(sha1_3), std::make_shared(3)); + l.insert(ih(sha1_4), std::make_shared(0)); + + // iteration order is the same as insertion order, not sort order of + // info-hashes + std::vector order; + for (auto i : l) + { + order.push_back(*i); + } + + TEST_CHECK((order == std::vector{1, 2, 3, 0})); + + TEST_EQUAL(*l[0], 1); + TEST_EQUAL(*l[1], 2); + TEST_EQUAL(*l[2], 3); + TEST_EQUAL(*l[3], 0); +} + +TORRENT_TEST(torrent_list_erase) +{ + aux::torrent_list l; + l.insert(v1, std::make_shared(1337)); + TEST_CHECK(!l.empty()); + + // this doesn't exist, returns false + TEST_CHECK(!l.erase(ih(sha1_2))); + TEST_CHECK(!l.empty()); + + TEST_EQUAL(*l.find(sha1_1), 1337); + TEST_CHECK(l.erase(ih(sha1_1))); + TEST_CHECK(l.find(sha1_1) == nullptr); + TEST_CHECK(l.empty()); +} + +TORRENT_TEST(torrent_list_erase2) +{ + aux::torrent_list l; + l.insert(ih(sha1_1), std::make_shared(1337)); + l.insert(ih(sha1_2), std::make_shared(1338)); + + TEST_EQUAL(*l.find(sha1_1), 1337); + TEST_EQUAL(l.size(), 2); + TEST_CHECK(!l.empty()); + + // delete an entry that isn't the last one + TEST_CHECK(l.erase(ih(sha1_1))); + TEST_CHECK(l.find(sha1_1) == nullptr); + TEST_EQUAL(l.size(), 1); + TEST_CHECK(!l.empty()); + TEST_EQUAL(*l.find(sha1_2), 1338); +} + +TORRENT_TEST(torrent_list_clear) +{ + aux::torrent_list l; + l.insert(ih(sha1_1), std::make_shared(1)); + l.insert(ih(sha1_2), std::make_shared(2)); + l.insert(ih(sha1_3), std::make_shared(3)); + l.insert(ih(sha1_4), std::make_shared(0)); + + TEST_CHECK(!l.empty()); + + TEST_CHECK(*l.find(sha1_1) == 1); + TEST_CHECK(*l.find(sha1_2) == 2); + TEST_CHECK(*l.find(sha1_3) == 3); + TEST_CHECK(*l.find(sha1_4) == 0); + + l.clear(); + TEST_CHECK(l.empty()); + + TEST_CHECK(l.find(sha1_1) == nullptr); + TEST_CHECK(l.find(sha1_2) == nullptr); + TEST_CHECK(l.find(sha1_3) == nullptr); + TEST_CHECK(l.find(sha1_4) == nullptr); +} + +#if !defined TORRENT_DISABLE_ENCRYPTION +TORRENT_TEST(torrent_list_obfuscated_lookup) +{ + aux::torrent_list l; + l.insert(ih(sha1_1), std::make_shared(1337)); + + TEST_EQUAL(*l.find(sha1_1), 1337); + static char const req2[4] = {'r', 'e', 'q', '2'}; + hasher h(req2); + h.update(sha1_1); + TEST_EQUAL(*l.find_obfuscated(h.final()), 1337); + // this should not exist as an obfuscated hash + TEST_CHECK(l.find_obfuscated(sha1_1) == nullptr); +} +#endif + 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 zcmYc>G_Xo8N=+;HqnF%zrIytMojQ>(Phvee?76f>)w)V%bPj7$?F z(^M0yg2a-H9Am4r{Csn(q{JeG_Xo8N=+Z&nfZANDL@68hGvGAhGqr^M#iZo zR+)Ke`6-rGX_+~x3MECUsVU}GhPtV_1tpa!239F%RynD8=_MJN2C1p3=2k|!#kq+& zIVduwhDND|hE@fcsmZCu3Pt((CB{Zp7t-sGa<=EC$$mB1o^k7x)r5+cb2eh>0{a|t z`^A2!%mf)`tecZql%9%in1KPNaS3Pk_LMOMUsGgV-OYDQ_fv$Yf)02 z@^>@HI1`+%HpMAphC?RR6dWMMImiKEVpWh>l92-sA7EI)g9j7{aCvAb0p(4s5=%;o zfPn*7XlQC-4p(YorB{%cVrm5uL^Tc=3~&RXA%$!qk}TBYIAo!o#~}+10AS=A8d>G0 zmLw{ar4|(fQ%WYtk$H)^K(ZvYxCAH*j$j37axgSAwlGOGgT}g{fq~WALx7Px>JQqAalBf#25C5uPPSn32V=b=HEIuEY;zap5@ltHJ|QRc3g;QH+eLB_vaHF z-Ys{F)n5Jm^P~7!a%brx8@2V?58hjU?C*$nj;zQ(VG<>6x@1xQi}~H5AV)%-kyr_I zX9_T-5lR|{Miy3o``)fN&BpM(mt(d-UbV!JZH%8*KiE`$Q{-WQyP$J|{`uCa#W^32 z?0en8)TVvi`OhBi!mp(c4kqt5CEh81dDY~+?>+l3FU4CgPq_GMf%^eAU7=O=!s)8c xyB@N9?w5J(roiH^%J=yNpV>LftB>M%M4p&WI{E3Nse03bZ<>WyPB*2d0st(XZ!!P? literal 0 HcmV?d00001 diff --git a/test/test_torrents/empty-files-2.torrent b/test/test_torrents/empty-files-2.torrent new file mode 100644 index 0000000000000000000000000000000000000000..8b24dc986de55b08447b5606883c3a5a69a6fffe GIT binary patch literal 927 zcmYc>G_Xo8N=+Z&nfZANDL@68hGvGAhGqr^mIkRN zR+)Ke`6-rGX_+~x3MECUsVU}GhPuVMi8(nb239F%RynD8=_MJNriMnThK5!JnW@RC z#R^6F`6b3iRu|Iik8-x>rOAFZ*q(9gl+}camUA{@=>q#4a{I-8sLV`FHMcU-P0cMR zsYEf%AQdEItecZql%9$r1GL7#0Mnv`Gkbf=7=o`UvaWGgj}`B+{FZfl@}eo{uC=u& zsZRO38DyLZPK!)&%9!DhNi_urNO2Bo0GL=6B$j04z(WWaTJQh@g(XxL>Sds;iB)1r zNf9vE;K~e5EzFHzN=>Zv3KCOHtssI(hCzY>ZXh(Ia99WRI1X8;=W)nF0{|EwhDKJo zsU?XDWvNBQz?6~+a%5g&E|4rqEiM7df}>smnj8$xj4e!3&7kSQ(7?dz?V&?$NtUA%HeIqP|Hb_7P>>^`&Pc2T zx-$is1_>n%Ln8~TzkP34oMvPA-pestAg@~D$2P`Ks~>DCzbW!Cz+KQeLH~T~)Z&~E zNA|t$U~1F8?)+yDcj4Dk2M3dPn-cF7zr1R4-uIsUmzUzLmnU3&wZQ!Vo37BRdf{|c x=Uoq3KKIMKc2i(+SLOTsg3s)n<<&=VJR(obC!PHC(Nw)@!8gsqE2o=MQvrxEZ#Dn` literal 0 HcmV?d00001 diff --git a/test/test_torrents/empty-files-3.torrent b/test/test_torrents/empty-files-3.torrent new file mode 100644 index 0000000000000000000000000000000000000000..a7a9468677be180916cbb1b3737716afa5405b30 GIT binary patch literal 927 zcmYc>G_Xo8N=+Z&nfZANDL@68hGvGAhGqtahQ_HT zR+)Ke`6-rGX_+~x3MECUsVU}GhPuVMi8(nb239F%RynD8=_MJNriMnThK5!JnW@RC z#R^6F`6b3iRu|Iik8-x>rOAFZ*q(9gl+}camUA{@=>q#4a{I-8sLV`FHMcU-%}Fdu zPenJ(zyQ;@gfn}4${2#LDYC9{SC19%vHX^Gd-9?w=dQK2D5*~QyBTDhv2JQ^K}jWw zaR#X%855i`rZ{EHaLA;Zf&-*D2Q>gptO^oKGIHP{1Pm<`tHhF$B4EJ4L(0(9!rTZL zuwbPoR(b`ADW+Bs!BiCEfI$E^5E@EA1L5W%$wEDiLl)|B9I{Z)7Px>JQqAalBf#25C5uPPSn32V=b=HEIuEY;zap5@ltHJ|QRc3g;QH+eLB_vaHF z-Ys{F)n5Jm^P~7!a%brx8@2V?58hjU?C*$nj;zQ(VG<>6x@1xQi}~H5AV)%-kyr_I zX9_S45=u0NMiy3o``)fN&BpM(mt(d-UbV!JZH%8*KiE`$Q{-WQyP$J|{`uCa#W^32 z?0en8)TVvi`OhBi!mp(c4kqt5CEh81dDY~+?>+l3FU4CgPq_GMf%^eAU7=O=!s)8c xyB@N9?w5J(roiH^%J=yNpV>LftB>M%M4p&WI{E3Nse03bZ<>WyPB*2d0sxL7Z!!P? literal 0 HcmV?d00001 diff --git a/test/test_torrents/empty-files-4.torrent b/test/test_torrents/empty-files-4.torrent new file mode 100644 index 0000000000000000000000000000000000000000..eabe83861f6660d859a7937f5963a8f57f9785ca GIT binary patch literal 927 zcmYc>G_Xo8N=+Z&nfZANDL@68hGvGAhGqtaCKjnC zR+)Ke`6-rGX_+~x3MECUsVU}GhPtV_1tpa!239F%RynD8=_MJN2C1p3=2k{HWsGsk znBbH#)y+vPN>4?x$JEfsz#!Go(5fIaH956dp(sDU#MsCx;mqEiGKS!5imYqg)nmnb zEWc&lp1f$vxod4LN~%--ZU#BdOt&~UF((JzI84JXq}LziY|l%R{c5m1BL^Nnz_3CJ9=I$tjBv<8!wH8hG^~(i zAprt69U5jp6(&}RB_&0`fPy>1(8So-3>c+gXPQ{)6(pvZT0sPnoC-7$rUo8nKpz_# zS>>jdBr24p78L_iN+w8AUSckgEJ-ac0m_0SL;;!{49$!!Oj6CDv1DjqU?uT|{o$*M z#d^Zp^P>5;&J9a-c%^5#^>)pt`;{FRV%kj}&EEa_1c!Ia-D0&@fB*a_K9<~By2wUt zz4n9m)+N{GnHgVP_4&?!$tTy2m9`XJj_ca1zVxxuwC~L)WkRzea zNUQ|9GlhthVQ6Gw^|$ZsiqmWi-+MV`3*=Qx{Mg3$Y4w9mfm7VZd2l&;+I!V&imf8|MF72_40&^uNJr;VAB;^RWF>b y>b&bA%jbTX*KP_d?y7vBU+|fov%LB!jz{E)`J|JdKANgGE%>Hcc;$3cYAOJ^-*kZh literal 0 HcmV?d00001 diff --git a/test/test_torrents/empty-files-5.torrent b/test/test_torrents/empty-files-5.torrent new file mode 100644 index 0000000000000000000000000000000000000000..79265e0a6a90c5e8d99179547ca0c44477f18b68 GIT binary patch literal 927 zcmYc>G_Xo8N=+Z&nfZANDL@68hGvGAhGqta=0>R| zR+)Ke`6-rGX_+~x3MECUsVU}GhPtV_1tpa!239F%RynD8=_MJN2C1p3=2k{HWsGsk znBbH#)h*6V%*jEq$JEd$)zHwYATu>NwOFAjKflD-$m&9R{ZY>LyfoRb2HP`kowAxx z(Q?j4EL~uqLvFv=50#l9rBL^Nnz_3CJ9=I$tjBv<8!wH8hG^~(i zK>-3c8yaFjc@wL|l9D1|K*610XlenDP_QFStn>;JQ%tQOf=CVp8V3v#xPj161Nzv| z$SOCrBvGL(wWt`FQZhld<|XC=$&%FK5}+(NN)({U!O+au!X(uU8c&7>23Btm9coLm zymfDODEA`kP8BYJ%;^>qU)UeMs#vTitUWK9f9u?^REJl3mRoPve7axRaUrJNDZsQrC^8L=EUfylZd-h*minm^#aPidw_XBLYLaXY9 z(^Z{!J!JXZFZ0?>fyG^w@AC^jvvZbLAI0&AJTaei^3z9C^`-^iGz+hsZc0rB036J3 AHvj+t literal 0 HcmV?d00001 diff --git a/test/test_torrents/empty_httpseed.torrent b/test/test_torrents/empty_httpseed.torrent new file mode 100644 index 0000000..5dea414 --- /dev/null +++ b/test/test_torrents/empty_httpseed.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e9:httpseedsl0:e4:infod6:lengthi425e4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡e8:url-listl0:ee diff --git a/test/test_torrents/empty_path.torrent b/test/test_torrents/empty_path.torrent new file mode 100644 index 0000000..05fb6aa --- /dev/null +++ b/test/test_torrents/empty_path.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl0:0:eee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/empty_path_multi.torrent b/test/test_torrents/empty_path_multi.torrent new file mode 100644 index 0000000..59815da --- /dev/null +++ b/test/test_torrents/empty_path_multi.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl5:abcdeeed6:lengthi3e4:pathl0:eed6:lengthi5e4:pathl0:eee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/hidden_parent_path.torrent b/test/test_torrents/hidden_parent_path.torrent new file mode 100644 index 0000000..6feeb91 --- /dev/null +++ b/test/test_torrents/hidden_parent_path.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl16:foo/../../../bar3:bareee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/httpseed.torrent b/test/test_torrents/httpseed.torrent new file mode 100644 index 0000000..2b5ea98 --- /dev/null +++ b/test/test_torrents/httpseed.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e9:httpseedsl18:http://foobar.com/e4:infod6:lengthi425e4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡e8:url-listl0:ee diff --git a/test/test_torrents/invalid_file_size.torrent b/test/test_torrents/invalid_file_size.torrent new file mode 100644 index 0000000..6589524 --- /dev/null +++ b/test/test_torrents/invalid_file_size.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld4:pathl3:foo7:bar.txte6:lengthli45eeed4:pathl3:foo7:var.txte6:lengthi24124eee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/invalid_filename.torrent b/test/test_torrents/invalid_filename.torrent new file mode 100644 index 0000000..6e2bfc5 --- /dev/null +++ b/test/test_torrents/invalid_filename.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1419490173e4:infod5:filesld6:lengthi51200e4:pathl1:feed6:lengthi14336e4:pathl1:.eee4:name5:\est212:piece lengthi16384e6:pieces80:01234567890123456789012345678901234567890123456789012345678901234567890123456789ee diff --git a/test/test_torrents/invalid_filename2.torrent b/test/test_torrents/invalid_filename2.torrent new file mode 100644 index 0000000..b63e174 --- /dev/null +++ b/test/test_torrents/invalid_filename2.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1419490173e4:infod5:filesld6:lengthi51200e4:pathl1:feed6:lengthi14335e4:pathl1:.1:.eed6:lengthi1e4:pathl1:/1:.eee4:name5:\est212:piece lengthi16384e6:pieces80:01234567890123456789012345678901234567890123456789012345678901234567890123456789ee diff --git a/test/test_torrents/invalid_info.torrent b/test/test_torrents/invalid_info.torrent new file mode 100644 index 0000000..45b16d1 --- /dev/null +++ b/test/test_torrents/invalid_info.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:info5:filese diff --git a/test/test_torrents/invalid_name.torrent b/test/test_torrents/invalid_name.torrent new file mode 100644 index 0000000..3dcc5d9 --- /dev/null +++ b/test/test_torrents/invalid_name.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl3:foo7:bar.txteed6:lengthi425e4:pathl3:foo7:var.txteee4:namei1348e12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/invalid_name2.torrent b/test/test_torrents/invalid_name2.torrent new file mode 100644 index 0000000..50c5d91 --- /dev/null +++ b/test/test_torrents/invalid_name2.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl3:foo7:bar.txteed6:lengthi425e4:pathl3:foo7:var.txteee4:name2:..12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/invalid_name3.torrent b/test/test_torrents/invalid_name3.torrent new file mode 100644 index 0000000..105c496 --- /dev/null +++ b/test/test_torrents/invalid_name3.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl3:foo7:bar.txteed6:lengthi425e4:pathl3:foo7:var.txteee4:name7:foobar 12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/invalid_path_list.torrent b/test/test_torrents/invalid_path_list.torrent new file mode 100644 index 0000000..257f2eb --- /dev/null +++ b/test/test_torrents/invalid_path_list.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathli1242e3:foo7:bar.txteed6:lengthi425e4:pathl3:foo7:var.txteee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/invalid_piece_len.torrent b/test/test_torrents/invalid_piece_len.torrent new file mode 100644 index 0000000..2cafba5 --- /dev/null +++ b/test/test_torrents/invalid_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 length5:163846:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/invalid_pieces.torrent b/test/test_torrents/invalid_pieces.torrent new file mode 100644 index 0000000..cd71cfb --- /dev/null +++ b/test/test_torrents/invalid_pieces.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 lengthi16384e6:piecesi-23eee diff --git a/test/test_torrents/invalid_symlink.torrent b/test/test_torrents/invalid_symlink.torrent new file mode 100644 index 0000000..8ef6c72 --- /dev/null +++ b/test/test_torrents/invalid_symlink.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi0e4:pathl1:a1:b3:bareed4:attr1:l6:lengthi425e4:pathl1:a1:b3:foo12:symlink pathl3:foo3:bari4eeeee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/large.torrent b/test/test_torrents/large.torrent new file mode 100644 index 0000000000000000000000000000000000000000..3056ec69587963cf7f6be29e9b78e932f04f23fb GIT binary patch literal 100144 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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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

    *NznXnm;(!yTN+6z5c)*~1xb}P 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Ξ=Gk`@r;#~i6xOc)sWX(n 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 literal 0 HcmV?d00001 diff --git a/test/test_torrents/v2_hybrid.torrent b/test/test_torrents/v2_hybrid.torrent new file mode 100644 index 0000000000000000000000000000000000000000..9a5c8762d6cfd0c2ebf61bace1498871aab7a648 GIT binary patch literal 91581 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 x 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 literal 0 HcmV?d00001 diff --git a/test/test_torrents/v2_incomplete_piece_layer.torrent b/test/test_torrents/v2_incomplete_piece_layer.torrent new file mode 100644 index 0000000000000000000000000000000000000000..3312e994c761fd37b74e6173bce6d918f1e99f1d GIT binary patch literal 64533 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 z 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 DFv#}c literal 0 HcmV?d00001 diff --git a/test/test_torrents/v2_large_file.torrent b/test/test_torrents/v2_large_file.torrent new file mode 100644 index 0000000..dc986a3 --- /dev/null +++ b/test/test_torrents/v2_large_file.torrent @@ -0,0 +1,17 @@ +d8:announce27:http://example.com/announce4:infod9:file treed7:test1MBd0:d6:lengthi17592186028032e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½eee12:meta versioni2e4:name7:test1MB12:piece lengthi4194304ee12:piece layersd32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½512:`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòBee diff --git a/test/test_torrents/v2_large_offset.torrent b/test/test_torrents/v2_large_offset.torrent new file mode 100644 index 0000000..2d99883 --- /dev/null +++ b/test/test_torrents/v2_large_offset.torrent @@ -0,0 +1,17 @@ +d8:announce27:http://example.com/announce4:infod9:file treed1:1d0:d6:lengthi17592186011648e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½ee1:2d0:d6:lengthi17592186011648e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½ee1:3d0:d6:lengthi17592186011648e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½ee1:4d0:d6:lengthi17592186011648e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½ee1:5d0:d6:lengthi17592186011648e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½ee1:6d0:d6:lengthi17592186011648e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½ee1:7d0:d6:lengthi17592186011648e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½ee1:8d0:d6:lengthi17592186011648e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½ee1:9d0:d6:lengthi17592186011648e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½ee1:ad0:d6:lengthi17592186011648e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½ee1:bd0:d6:lengthi17592186011648e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½ee1:cd0:d6:lengthi17592186011648e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½ee1:dd0:d6:lengthi17592186011648e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½ee1:ed0:d6:lengthi17592186011648e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½ee1:fd0:d6:lengthi17592186011648e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½eee12:meta versioni2e4:name7:test1MB12:piece lengthi65536ee12:piece layersd32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½512:`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòBee diff --git a/test/test_torrents/v2_mismatching_metadata.torrent b/test/test_torrents/v2_mismatching_metadata.torrent new file mode 100644 index 0000000..c76cad7 --- /dev/null +++ b/test/test_torrents/v2_mismatching_metadata.torrent @@ -0,0 +1,17 @@ +d8:announce27:http://example.com/announce4:infod9:file treed7:/est1MBd0:d6:lengthi1048576e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½eee6:lengthi1048576e12:meta versioni2e4:name7:test1MB12:piece lengthi65536e6:pieces320:ܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùae12:piece layersd32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½512:`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòBee diff --git a/test/test_torrents/v2_missing_file_root_invalid_symlink.torrent b/test/test_torrents/v2_missing_file_root_invalid_symlink.torrent new file mode 100644 index 0000000000000000000000000000000000000000..6cf1125ad4d4e470717d051d1d348da08f3348f5 GIT binary patch literal 96517 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%0A(*{Ef>?HgOc3iA>-|?tWv>aT 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 literal 0 HcmV?d00001 diff --git a/test/test_torrents/v2_multipiece_file.torrent b/test/test_torrents/v2_multipiece_file.torrent new file mode 100644 index 0000000..3bacf47 --- /dev/null +++ b/test/test_torrents/v2_multipiece_file.torrent @@ -0,0 +1,17 @@ +d8:announce27:http://example.com/announce4:infod9:file treed7:test1MBd0:d6:lengthi1048576e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½eee6:lengthi1048576e12:meta versioni2e4:name7:test1MB12:piece lengthi65536e6:pieces320:ܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùaܕ¾¾žêŒ-@Í«zuÄùae12:piece layersd32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½512:`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòBee \ No newline at end of file diff --git a/test/test_torrents/v2_multiple_files.torrent b/test/test_torrents/v2_multiple_files.torrent new file mode 100644 index 0000000000000000000000000000000000000000..c3f9b43c6f891f8c893eb9ff05f6f4c7d4aa2c18 GIT binary patch literal 96605 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 V(x*Rb%eoE5?|>f@P%{cfWo3&ec^m)$ literal 0 HcmV?d00001 diff --git a/test/test_torrents/v2_no_piece_layers.torrent b/test/test_torrents/v2_no_piece_layers.torrent new file mode 100644 index 0000000..c351195 --- /dev/null +++ b/test/test_torrents/v2_no_piece_layers.torrent @@ -0,0 +1 @@ +d8:announce27:http://example.com/announce4:infod9:file treed7:test1MBd0:d6:lengthi1048576e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½eee12:meta versioni2e4:name7:test1MB12:piece lengthi65536eee diff --git a/test/test_torrents/v2_no_power2_piece.torrent b/test/test_torrents/v2_no_power2_piece.torrent new file mode 100644 index 0000000..60bdcf4 --- /dev/null +++ b/test/test_torrents/v2_no_power2_piece.torrent @@ -0,0 +1 @@ +d8:announce27:http://example.com/announce4:infod9:file treed7:test1MBd0:d6:lengthi1048576e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½eee12:meta versioni2e4:name7:test1MB12:piece lengthi32767eee diff --git a/test/test_torrents/v2_non_multiple_piece_layer.torrent b/test/test_torrents/v2_non_multiple_piece_layer.torrent new file mode 100644 index 0000000..ab721e1 --- /dev/null +++ b/test/test_torrents/v2_non_multiple_piece_layer.torrent @@ -0,0 +1,17 @@ +d8:announce27:http://example.com/announce4:infod9:file treed7:test1MBd0:d6:lengthi1048576e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½eee12:meta versioni2e4:name7:test1MB12:piece lengthi65536ee12:piece layersd32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½513:a`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòBee diff --git a/test/test_torrents/v2_only.torrent b/test/test_torrents/v2_only.torrent new file mode 100644 index 0000000..a7c6116 --- /dev/null +++ b/test/test_torrents/v2_only.torrent @@ -0,0 +1,17 @@ +d8:announce27:http://example.com/announce4:infod9:file treed7:test1MBd0:d6:lengthi1048576e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½eee12:meta versioni2e4:name7:test1MB12:piece lengthi65536ee12:piece layersd32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½512:`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòBee \ No newline at end of file diff --git a/test/test_torrents/v2_overlong_integer.torrent b/test/test_torrents/v2_overlong_integer.torrent new file mode 100644 index 0000000..7620f10 --- /dev/null +++ b/test/test_torrents/v2_overlong_integer.torrent @@ -0,0 +1,2 @@ +d10:created by10:libtorrent13:creation datei1556473052e4:infod9:file treed9:test1.txtd0:d6:lengthi07e11:pieces root32:®Àpd_å>ã³v0Y7a4ðXÌ3rGÉx­Ñx¶Ìß°Ÿee9:test2.txtd0:d6:lengthi7e11:pieces root32:f­$ +Ãô„®Uêɝbón œ±æ€Ê8×H¡ee9:test3.txtd0:d6:lengthi7e11:pieces root32:þ/ŸRÂ[íbŒ¸n҅œç={6oNµîæÅ°™VShÖeee12:meta versioni2e4:name4:test12:piece lengthi16384ee12:piece layersdee diff --git a/test/test_torrents/v2_piece_layer_invalid_file_hash.torrent b/test/test_torrents/v2_piece_layer_invalid_file_hash.torrent new file mode 100644 index 0000000..0431020 --- /dev/null +++ b/test/test_torrents/v2_piece_layer_invalid_file_hash.torrent @@ -0,0 +1,17 @@ +d8:announce27:http://example.com/announce4:infod9:file treed7:test1MBd0:d6:lengthi1048576e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½eee12:meta versioni2e4:name7:test1MB12:piece lengthi65536ee12:piece layersd33:aQ^©D¸tMí.ŽÆ¨E RâKPwóÿ½512:`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòB`ªéÇ´(ø~è‚)Ꮰ+ßÍ{" ÝŠ’»$…ëzòBee diff --git a/test/test_torrents/v2_piece_size.torrent b/test/test_torrents/v2_piece_size.torrent new file mode 100644 index 0000000..29e8dc8 --- /dev/null +++ b/test/test_torrents/v2_piece_size.torrent @@ -0,0 +1 @@ +d8:announce27:http://example.com/announce4:infod9:file treed7:test1MBd0:d6:lengthi1048576e11:pieces root32:Q^©D¸tMí.ŽÆ¨E RâKPwóÿ½eee12:meta versioni2e4:name7:test1MB12:piece lengthi2147483647eee diff --git a/test/test_torrents/v2_symlinks.torrent b/test/test_torrents/v2_symlinks.torrent new file mode 100644 index 0000000000000000000000000000000000000000..d1196476c58d49f290b97b8f2d630f2ed5da0ad3 GIT binary patch literal 3290 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%0Daycr+@T+WUU`7rsdZ(&Ht0fCv z)n>)Bbq%5HSv%tUors^eq|qfSvLA#KZ*nn&T|r&ZG|GlV>?>~mP3oW!w;px96F(;)yh#s&BMJL?!Iq2VwX{<-B8rGDD z8%2(Zp2`il9vWWUu2)jmsQzuSc%Q)5GC5*kQ`vD@K0Cqgcw*Bcchu-H6TTvi=IT^+ zJ@)ZrR+_AwSK!b-M1Uvj?qMCAYEY%kTsNw04XwIl)EK1!Uz-Ytowuo5@b7%P#3F^} zAqftu%J6$4V>fNpDAYyJNfi9#eQk5g9VSaXB*O{p*DRUuKGHhX29vubEA(V7V&BBz zQ<6#Rdk4~bAg-IMP#0O|G8EmZXCQlp`)OL5ch`(jUP!yQLjGh0z@#Vt3J={TllaMm h?xY;Pb9pD0^}QXFW+w}}mK}Jk#$ulLzw1Y%{{?^?9JBxc literal 0 HcmV?d00001 diff --git a/test/test_torrents/v2_unordered_files.torrent b/test/test_torrents/v2_unordered_files.torrent new file mode 100644 index 0000000..a4a4a0f --- /dev/null +++ b/test/test_torrents/v2_unordered_files.torrent @@ -0,0 +1,2 @@ +d10:created by10:libtorrent13:creation datei1556472816e4:infod9:file treed9:test2.txtd0:d6:lengthi7e11:pieces root32:f­$ +Ãô„®Uêɝbón œ±æ€Ê8×H¡ee9:test1.txtd0:d6:lengthi7e11:pieces root32:®Àpd_å>ã³v0Y7a4ðXÌ3rGÉx­Ñx¶Ìß°Ÿee9:test3.txtd0:d6:lengthi7e11:pieces root32:þ/ŸRÂ[íbŒ¸n҅œç={6oNµîæÅ°™VShÖeee12:meta versioni2e4:name4:test12:piece lengthi16384ee12:piece layersdee diff --git a/test/test_torrents/v2_zero_root.torrent b/test/test_torrents/v2_zero_root.torrent new file mode 100644 index 0000000000000000000000000000000000000000..7665240ba5b56701f35f91a5c3d318fa23ee0cc6 GIT binary patch literal 767 zcmYeXuu9C!%P-AKPBk*O$|xx*u+rC0tw_u*$Vt^p&d=3{$(mSY=B4GQSX!lJ=AYxv3?I3T3H9#hLkenMSE5R(XlJsc@Ts3c!XcKrJ&fH8nN^>w^j>RsyxA z5Mi4s(5Qq}FOP50_)*6${Gv(oVL#V>p|jOW3+{GJ+O5+1y6ThDsQmB_pVU+UHt=S-JTVWqn$CYau>vrz2{d+?L@BuciKJ5Si literal 0 HcmV?d00001 diff --git a/test/test_torrents/whitespace_url.torrent b/test/test_torrents/whitespace_url.torrent new file mode 100644 index 0000000..2c2cdbe --- /dev/null +++ b/test/test_torrents/whitespace_url.torrent @@ -0,0 +1 @@ +d8:announce25: udp://test.com/announce10:created by10:libtorrent13:creation datei1359599503e4:infod6:lengthi425e4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/zero.torrent b/test/test_torrents/zero.torrent new file mode 100644 index 0000000..601d9da --- /dev/null +++ b/test/test_torrents/zero.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod6:lengthi0e4:name4:temp12:piece lengthi16384e6:pieces0:ee diff --git a/test/test_torrents/zero2.torrent b/test/test_torrents/zero2.torrent new file mode 100644 index 0000000..e17777f --- /dev/null +++ b/test/test_torrents/zero2.torrent @@ -0,0 +1 @@ +d8:announce41:udp://tracker.opentracker.com:80/announce13:announce-listll41:udp://tracker.opentracker.com:80/announceel32:tracker.publicbt.com:80/announceee7:comment14:sample comment10:created by10:libtorrent13:creation datei1418787579e4:infod5:filesld6:lengthi0e4:pathl14:text_file2.txteed4:attr1:p6:lengthi0e4:pathl17:.____padding_file1:0eed6:lengthi0e4:pathl13:text_file.txte4:sha120:ababababababababababee4:name6:sample12:piece lengthi16384e6:pieces0:ee diff --git a/test/test_tracker.cpp b/test/test_tracker.cpp new file mode 100644 index 0000000..0d94d89 --- /dev/null +++ b/test/test_tracker.cpp @@ -0,0 +1,734 @@ +/* + +Copyright (c) 2011-2020, Arvid Norberg +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016, terry zhao +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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" // for supports_ipv6 +#include "test_utils.hpp" +#include "udp_tracker.hpp" +#include "settings.hpp" +#include "test_utils.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/peer_info.hpp" // for peer_list_entry +#include "libtorrent/alert_types.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/http_tracker_connection.hpp" // for parse_tracker_response +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/socket_io.hpp" + +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, {}, 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, {}, 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, {}, 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, {}, 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, {}, 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, tracker_request::scrape_request, 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, tracker_request::scrape_request, 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, {}, 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, {}, 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); + pack.set_int(settings_pack::alert_queue_size, 10000); + + auto s = std::make_unique(pack); + + error_code ec; + remove_all("tmp1_tracker", ec); + create_directory("tmp1_tracker", ec); + 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 + 2) + break; + + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + // expect two announces, one each for v1 and v2 + TEST_EQUAL(num_udp_announces(), prev_udp_announces + 2); + + // 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 + 4) + 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 + 4); + + 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, test_listen_interface()); + + auto s = std::make_unique(pack); + + error_code ec; + remove_all("tmp2_tracker", ec); + create_directory("tmp2_tracker", ec); + 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, test_listen_interface()); + + auto s = std::make_unique(pack); + + error_code ec; + remove_all("tmp3_tracker", ec); + create_directory("tmp3_tracker", ec); + 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, test_listen_interface()); + + 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); + + auto s = std::make_unique(pack); + + error_code ec; + remove_all("tmp2_tracker", ec); + create_directory("tmp2_tracker", ec); + 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 +#ifndef TORRENT_DISABLE_ALERT_MSG +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); + 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 + // expect announces for v1 and v2 info hashes + for (int i = 0; i < 2; ++i) + wait_for_alert(s, tracker_reply_alert::alert_type, "s"); + + s.remove_torrent(h); + + int const count = count_stopped_events(s, (timeout == 0) ? 0 : 2); + TEST_EQUAL(count, (timeout == 0) ? 0 : 2); +} +} // 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 +#endif diff --git a/test/test_transfer.cpp b/test/test_transfer.cpp new file mode 100644 index 0000000..cf8afe0 --- /dev/null +++ b/test/test_transfer.cpp @@ -0,0 +1,382 @@ +/* + +Copyright (c) 2008-2010, 2012-2020, Arvid Norberg +Copyright (c) 2016, 2018, 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/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/session_params.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; +} + +struct transfer_tag; +using transfer_flags_t = lt::flags::bitfield_flag; + +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 ==== move-storage: %s\n\n\n" + , test_name[proxy_type] + , (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, test_listen_interface()); + + 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, test_listen_interface()); + 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 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, ¶ms); + + int num_pieces = tor2.torrent_file()->num_pieces(); + std::vector priorities(std::size_t(num_pieces), 1); + + 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. + + if (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); + + if (peer_disconnects >= 2) break; + + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + 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(); +} +*/ +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(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_truncate.cpp b/test/test_truncate.cpp new file mode 100644 index 0000000..b3c75e6 --- /dev/null +++ b/test/test_truncate.cpp @@ -0,0 +1,107 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/aux_/path.hpp" +#include "libtorrent/truncate.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/error_code.hpp" + +namespace { + +void create_file(std::string const& name, int size) +{ + lt::error_code ec; + lt::create_directories(lt::parent_path(name), ec); + TEST_CHECK(!ec); + std::ofstream f(name.c_str()); + std::vector buf(static_cast(size)); + f.write(buf.data(), std::streamsize(buf.size())); +} + +std::int64_t file_size(std::string const& name) +{ + lt::file_status st; + lt::error_code ec; + lt::stat_file(name, &st, ec); + std::cerr << name << ": " << ec.message() << '\n'; + TEST_CHECK(!ec); + return st.file_size; +} +} + +TORRENT_TEST(truncate_small_files) +{ + using lt::combine_path; + + lt::file_storage fs; + fs.add_file(combine_path("test", "a"), 100); + fs.add_file(combine_path("test", "b"), 900); + fs.add_file(combine_path("test", "c"), 10); + + create_file(combine_path("test", "a"), 99); + create_file(combine_path("test", "b"), 899); + create_file(combine_path("test", "c"), 9); + + lt::storage_error err; + lt::truncate_files(fs, ".", err); + TEST_CHECK(!err.ec); + + TEST_EQUAL(file_size(combine_path("test", "a")), 99); + TEST_EQUAL(file_size(combine_path("test", "b")), 899); + TEST_EQUAL(file_size(combine_path("test", "c")), 9); +} + +TORRENT_TEST(truncate_large_files) +{ + using lt::combine_path; + + lt::file_storage fs; + fs.add_file(combine_path("test", "a"), 100); + fs.add_file(combine_path("test", "b"), 900); + fs.add_file(combine_path("test", "c"), 10); + + create_file(combine_path("test", "a"), 101); + create_file(combine_path("test", "b"), 901); + create_file(combine_path("test", "c"), 11); + + lt::storage_error err; + lt::truncate_files(fs, ".", err); + TEST_CHECK(!err.ec); + + TEST_EQUAL(file_size(combine_path("test", "a")), 100); + TEST_EQUAL(file_size(combine_path("test", "b")), 900); + TEST_EQUAL(file_size(combine_path("test", "c")), 10); +} diff --git a/test/test_upnp.cpp b/test/test_upnp.cpp new file mode 100644 index 0000000..15bef44 --- /dev/null +++ b/test/test_upnp.cpp @@ -0,0 +1,363 @@ +/* + +Copyright (c) 2007-2010, 2012-2013, 2015-2020, Arvid Norberg +Copyright (c) 2015, Mike Tzou +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 "broadcast_socket.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, aux::listen_socket_handle const&) 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 + , aux::listen_socket_handle const&) 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_context 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; + for (auto const& face : ifs) + { + if (!face.interface_address.is_v4()) continue; + std::cout << " - " << idx + << ' ' << face.interface_address.to_string() + << ' ' << int(static_cast(face.state)) + << ' ' << static_cast(face.flags) + << ' ' << face.name << '\n'; + ++idx; + } + + std::printf("%-17s%-17s%s\n", "destination", "network", "interface"); + for (auto const& r : routes) + { + if (!r.destination.is_v4()) continue; + std::printf("%-17s%-17s%s\n" + , r.destination.to_string().c_str() + , r.netmask.to_string().c_str() + , r.name); + } + + auto const iface = std::find_if(ifs.begin(), ifs.end(), [&](ip_interface const& face) + { + if (!face.interface_address.is_v4()) return false; + if (!(face.flags & if_flags::up)) return false; + if (!(face.flags & if_flags::multicast)) return false; + if (face.state != if_state::up && face.state != if_state::unknown) return false; + + auto const route = std::find_if(routes.begin(), routes.end(), [&](ip_route const& r) + { + if (!r.destination.is_v4()) return false; + if (string_view(face.name) != r.name) return false; + return match_addr_mask(make_address_v4("239.255.255.250") + , r.destination.to_v4() + , r.netmask); + }); + 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(uep("239.255.255.250", 1900)); + + lt::io_context ios; + aux::session_settings sett; + + // pick an appropriate interface to run this test on + auto const ipf = pick_upnp_interface(); + + sock->open(&incoming_msearch, ios, ec); + + upnp_callback cb; + auto upnp_handler = std::make_shared(ios, sett, cb + , ipf.interface_address.to_v4(), ipf.netmask.to_v4(), ipf.name, aux::listen_socket_handle()); + upnp_handler->start(); + + for (int i = 0; i < 20; ++i) + { + ios.restart(); + ios.poll(); + 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.restart(); + ios.poll(); + 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.restart(); + ios.poll(); + 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_wipconn) +{ + run_upnp_test(combine_path("..", "root1.xml").c_str(), "wipconn", 1); +} + +TORRENT_TEST(upnp_wanipconnection) +{ + run_upnp_test(combine_path("..", "root2.xml").c_str(), "WANIPConnection", 1); +} + +TORRENT_TEST(upnp_wanipconnection2) +{ + run_upnp_test(combine_path("..", "root3.xml").c_str(), "WANIPConnection_2", 2); +} + +TORRENT_TEST(upnp_max_mappings) +{ + lt::io_context ios; + + // pick an appropriate interface to run this test on + 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, aux::listen_socket_handle()); + + 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..798c3cf --- /dev/null +++ b/test/test_url_seed.cpp @@ -0,0 +1,67 @@ +/* + +Copyright (c) 2014-2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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; + +#if TORRENT_USE_SSL +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..c74d882 --- /dev/null +++ b/test/test_utf8.cpp @@ -0,0 +1,147 @@ +/* + +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2018, Alden Torres +Copyright (c) 2014, 2017, 2019-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 "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"); + + // surrogates not allowed + parse_error("\xed\xb8\x88"); +} diff --git a/test/test_utils.cpp b/test/test_utils.cpp new file mode 100644 index 0000000..4c13843 --- /dev/null +++ b/test/test_utils.cpp @@ -0,0 +1,149 @@ +/* + +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2015-2016, 2019-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 "test_utils.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/aux_/merkle.hpp" +#include "libtorrent/random.hpp" + +#ifdef _WIN32 +#include +#include // for _O_WRONLY +#endif + +namespace libtorrent +{ + std::string time_now_string() + { + return time_to_string(clock_type::now()); + } + + std::string time_to_string(time_point const tp) + { + static const time_point start = clock_type::now(); + char ret[200]; + int t = int(total_milliseconds(tp - 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; + } + + std::string test_listen_interface() + { + static int port = int(random(10000) + 10000); + char ret[200]; + std::snprintf(ret, sizeof(ret), "0.0.0.0:%d", port); + ++port; + return ret; + } +} + +using namespace lt; + +aux::vector build_tree(int const size) +{ + int const num_leafs = merkle_num_leafs(size); + aux::vector full_tree(merkle_num_nodes(num_leafs)); + + for (int i = 0; i < size; i++) + { + std::uint32_t hash[32 / 4]; + std::fill(std::begin(hash), std::end(hash), i + 1); + full_tree[full_tree.end_index() - num_leafs + i] = sha256_hash(reinterpret_cast(hash)); + } + + merkle_fill_tree(full_tree, num_leafs); + return full_tree; +} + +#ifdef _WIN32 +int EXPORT truncate(char const* file, std::int64_t size) +{ + int fd = ::_open(file, _O_WRONLY); + if (fd < 0) return -1; + int const err = ::_chsize_s(fd, size); + ::_close(fd); + if (err == 0) return 0; + errno = err; + return -1; +} +#endif + +ofstream::ofstream(char const* filename) +{ + exceptions(std::ofstream::failbit); + native_path_string const name = convert_to_native_path_string(filename); + open(name.c_str(), std::fstream::out | std::fstream::binary); +} + +bool exists(std::string const& f) +{ + lt::error_code ec; + return lt::exists(f, ec); +} + +std::vector serialize(lt::torrent_info const& ti) +{ + lt::create_torrent ct(ti); + ct.set_creation_date(0); + entry e = ct.generate(); + std::vector out_buffer; + bencode(std::back_inserter(out_buffer), e); + return out_buffer; +} + +lt::file_storage make_files(std::vector const files, int const piece_size) +{ + file_storage fs; + int i = 0; + for (auto const& e : files) + { + char filename[200]; + std::snprintf(filename, sizeof(filename), "t/test%d", int(i++)); + fs.add_file(filename, e.size, e.pad ? file_storage::flag_pad_file : file_flags_t{}); + } + + fs.set_piece_length(piece_size); + fs.set_num_pieces(aux::calc_num_pieces(fs)); + + return fs; +} + diff --git a/test/test_utils.hpp b/test/test_utils.hpp new file mode 100644 index 0000000..abf4538 --- /dev/null +++ b/test/test_utils.hpp @@ -0,0 +1,89 @@ +/* + +Copyright (c) 2015, 2017, 2019-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 TEST_UTILS_HPP +#define TEST_UTILS_HPP + +#include + +#include "test.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/download_priority.hpp" +#include "libtorrent/fwd.hpp" + +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/path.hpp" +#include +#include + +namespace libtorrent +{ + EXPORT std::string time_now_string(); + EXPORT std::string time_to_string(lt::time_point const tp); + EXPORT std::string test_listen_interface(); +} + +constexpr inline lt::download_priority_t operator "" _pri(unsigned long long const p) +{ return lt::download_priority_t(static_cast(p)); } + +constexpr inline lt::file_index_t operator "" _file(unsigned long long const p) +{ return lt::file_index_t(static_cast(p)); } + +constexpr inline lt::piece_index_t operator "" _piece(unsigned long long const p) +{ return lt::piece_index_t(static_cast(p)); } + +EXPORT std::vector serialize(lt::torrent_info const& ti); + +EXPORT lt::aux::vector build_tree(int const size); + +#ifdef _WIN32 +int EXPORT truncate(char const* file, std::int64_t size); +#endif + +struct EXPORT ofstream : std::ofstream +{ + ofstream(char const* filename); +}; + +EXPORT bool exists(std::string const& f); + +struct file_ent +{ + std::int64_t size; + bool pad; +}; + +EXPORT lt::file_storage make_files(std::vector files, int piece_size); + +#endif + diff --git a/test/test_utp.cpp b/test/test_utp.cpp new file mode 100644 index 0000000..008e33a --- /dev/null +++ b/test/test_utp.cpp @@ -0,0 +1,169 @@ +/* + +Copyright (c) 2010, 2012-2019, Arvid Norberg +Copyright (c) 2016-2018, 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/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/aux_/utp_stream.hpp" +#include +#include + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" +#include "test_utils.hpp" +#include + +#ifdef TORRENT_UTP_LOG_ENABLE +#include "libtorrent/utp_stream.hpp" +#endif + +using namespace lt; + +namespace { + +void test_transfer() +{ +#ifdef TORRENT_UTP_LOG_ENABLE + lt::set_utp_stream_logging(true); +#endif + + // 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, test_listen_interface()); + lt::session ses1(pack); + + pack.set_str(settings_pack::listen_interfaces, test_listen_interface()); + 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) +{ + using lt::aux::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..67faf7b --- /dev/null +++ b/test/test_web_seed.cpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2007-2008, 2010, 2013-2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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; + +#if TORRENT_USE_SSL +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..23f0a0d --- /dev/null +++ b/test/test_web_seed_ban.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2013, 2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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; + +#if TORRENT_USE_SSL +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..bad6422 --- /dev/null +++ b/test/test_web_seed_chunked.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2013, 2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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; + +#if TORRENT_USE_SSL +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..5d04890 --- /dev/null +++ b/test/test_web_seed_http.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2013, 2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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); +} + +#if TORRENT_USE_SSL +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..98daa14 --- /dev/null +++ b/test/test_web_seed_http_pw.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2013, 2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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); +} + +#if TORRENT_USE_SSL +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..442b98c --- /dev/null +++ b/test/test_web_seed_redirect.cpp @@ -0,0 +1,99 @@ +/* + +Copyright (c) 2014-2017, 2019-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, 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 "test_utils.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" +#include "libtorrent/session_params.hpp" +#include "libtorrent/aux_/open_mode.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); + + ofstream("test_file").write(random_data.data(), random_data.size()); + 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); + + 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..e4e352c --- /dev/null +++ b/test/test_web_seed_socks4.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2013, 2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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; + +#if TORRENT_USE_SSL +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..98eca20 --- /dev/null +++ b/test/test_web_seed_socks5.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2013, 2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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; + +#if TORRENT_USE_SSL +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..8438ea6 --- /dev/null +++ b/test/test_web_seed_socks5_no_peers.cpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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) +{ +#if TORRENT_USE_SSL + 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..e1da9e4 --- /dev/null +++ b/test/test_web_seed_socks5_pw.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2013, 2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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; + +#if TORRENT_USE_SSL +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..080bce3 --- /dev/null +++ b/test/test_xml.cpp @@ -0,0 +1,493 @@ +/* + +Copyright (c) 2013-2017, 2019, Arvid Norberg +Copyright (c) 2015, Mike Tzou +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2018, 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/xml_parse.hpp" +#include "libtorrent/upnp.hpp" +#include "test.hpp" +#include +#include + +namespace { + +char upnp_xml[] = +R"( + +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[] = +R"( + +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[] = +R"( + + +s:Client +UPnPError + + +402 +Invalid Args + + + + +)"; + +char upnp_xml4[] = +R"( + + + +123.10.20.30 + + +)"; + +char upnp_xml5[] = +R"( +http://192.168.1.1:49152 + + + + urn:schemas-upnp-org:device:WANDevice:1 + + + urn:schemas-upnp-org:device:WANConnectionDevice:1 + WANConnectionDevice + + + urn:schemas-upnp-org:service:WANIPConnection:1 + urn:upnp-org:serviceId:WANIPConn1 + /wipc_scpd.xml + /wipc_cont + /wipc_evnt + + + urn:schemas-upnp-org:service:WANPPPConnection:1 + urn:upnp-org:serviceId:WANPPPConnection + /wpppc_scpd.xml + /wpppc_cont + /wpppc_evnt + + + + + + + http://192.168.1.1/index.htm + +)"; + +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.service_type, "urn:schemas-upnp-org:service:WANIPConnection:1"); + 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.service_type, "urn:schemas-upnp-org:service:WANPPPConnection:1"); + 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(upnp_parser5) +{ + parse_state xml_s; + xml_parse(upnp_xml5, 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, "/wipc_cont"); + TEST_EQUAL(xml_s.service_type, "urn:schemas-upnp-org:service:WANIPConnection:1"); +} + +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; + settings_pack pack; + // we use a self-signed cert for HTTPS trackers, the test would fail if we + // tried to validate it. + if (protocol == "https"_sv) + pack.set_bool(settings_pack::validate_https_trackers, false); + if (proxy) + { + proxy_port = start_proxy(proxy); + if (proxy_port < 0) + { + std::printf("failed to start proxy"); + return; + } + 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); + } + else + { + 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); + } + + 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); + + // 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, test_listen_interface()); + 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(session_params{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(0_file, 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..0612b91 --- /dev/null +++ b/test/web_seed_suite.hpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2013-2017, 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 "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..6df8581 --- /dev/null +++ b/test/web_server.py @@ -0,0 +1,226 @@ +#!/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 = 250 + + def handle_timeout(self): + print('TIMEOUT') + 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(self): + + print('INCOMING-REQUEST [from: {}]: {}'.format(self.request.getsockname(), self.requestline)) + print(self.headers) + sys.stdout.flush() + + global chunked_encoding + global keepalive + + # if the request contains the hostname and port. strip it + if self.path.startswith('http://') or self.path.startswith('https://'): + self.path = self.path[8:] + self.path = self.path[self.path.find('/'):] + + file_path = os.path.normpath(self.path) + sys.stdout.flush() + + if self.path == '/password_protected': + passed = False + if 'Authorization' in self.headers: + auth = self.headers['Authorization'] + passed = auth == 'Basic %s' % base64.b64encode(b'testuser:testpass').decode() + + if not passed: + self.send_response(401) + self.send_header("Connection", "close") + self.end_headers() + return + + self.path = '/test_file' + file_path = os.path.normpath('/test_file') + + if self.path == '/redirect': + self.send_response(301) + self.send_header("Location", "/test_file") + self.send_header("Connection", "close") + self.end_headers() + elif self.path == '/infinite_redirect': + self.send_response(301) + self.send_header("Location", "/infinite_redirect") + self.send_header("Connection", "close") + self.end_headers() + elif self.path == '/relative/redirect': + self.send_response(301) + self.send_header("Location", "../test_file") + self.send_header("Connection", "close") + self.end_headers() + elif self.path.startswith('/announce'): + self.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' + self.send_header("Content-Length", "%d" % len(response)) + self.send_header("Connection", "close") + self.end_headers() + self.wfile.write(response) + self.request.close() + elif os.path.split(self.path)[1].startswith('seed?'): + query = self.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(self.path[1:self.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() + + self.send_response(200) + print('sending %d bytes' % len(data)) + sys.stdout.flush() + self.send_header("Content-Length", "%d" % len(data)) + self.end_headers() + self.wfile.write(data) + except Exception as e: + print('FILE ERROR: ', filename, e) + traceback.print_exc(file=sys.stdout) + sys.stdout.flush() + self.send_response(404) + self.send_header("Content-Length", "0") + try: + self.end_headers() + except Exception: + pass + 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 self.headers: + self.send_response(206) + st, e = self.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 + self.send_header('Content-Range', 'bytes ' + str(start_range) + + '-' + str(end_range - 1) + '/' + str(size)) + else: + self.send_response(200) + self.send_header('Accept-Ranges', 'bytes') + if chunked_encoding: + self.send_header('Transfer-Encoding', 'chunked') + self.send_header('Content-Length', end_range - start_range) + if filename.endswith('.gz'): + self.send_header('Content-Encoding', 'gzip') + if not keepalive: + self.send_header("Connection", "close") + if not use_ssl: + self.request.shutdown(socket.SHUT_RD) + + self.end_headers() + + f.seek(start_range) + length = end_range - start_range + while length > 0: + to_send = min(length, 0x900) + if chunked_encoding: + self.wfile.write(b'%x\r\n' % to_send) + data = f.read(to_send) + print('read %d bytes' % to_send) + sys.stdout.flush() + self.wfile.write(data) + if chunked_encoding: + self.wfile.write(b'\r\n') + length -= to_send + print('sent %d bytes (%d bytes left)' % (len(data), length)) + sys.stdout.flush() + if chunked_encoding: + self.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() + self.send_response(404) + self.send_header("Content-Length", "0") + try: + self.end_headers() + except Exception: + pass + + print("...DONE") + sys.stdout.flush() + self.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..0acc738 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(dht dht_put.cpp) +target_link_libraries(dht PRIVATE torrent-rasterbar) + +add_executable(dht_sample dht_sample.cpp) +target_link_libraries(dht_sample 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..9665be4 --- /dev/null +++ b/tools/Jamfile @@ -0,0 +1,47 @@ +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) ; +} + +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 + 14 + 64 + ; + +exe dht : dht_put.cpp : ../ed25519/src ; +exe dht-sample : dht_sample.cpp : ../ed25519/src ; +exe session_log_alerts : session_log_alerts.cpp ; +exe disk_io_stress_test : disk_io_stress_test.cpp ; + diff --git a/tools/benchmark_checking.py b/tools/benchmark_checking.py new file mode 100755 index 0000000..2f40336 --- /dev/null +++ b/tools/benchmark_checking.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +import argparse +import os +import platform +import shutil +import subprocess +import sys +import time + +from linux_vmstat import capture_sample +from linux_vmstat import plot_output +from linux_vmstat import print_output_to_file + + +def main(): + args = parse_args() + + ret = os.system('cd ../examples && b2 release %s stage_client_test stage_connection_tester' + % args.toolset) + if ret != 0: + print('ERROR: build failed: %d' % ret) + sys.exit(1) + + rm_file_or_dir(".ses_state") + rm_file_or_dir(".resume") + + if not os.path.exists('checking_benchmark.torrent'): + ret = os.system('../examples/connection_tester gen-torrent -s 10000 -n 15 -t checking_benchmark.torrent') + if ret != 0: + print('ERROR: connection_tester failed: %d' % ret) + sys.exit(1) + + if not os.path.exists("checking_benchmark.torrent"): + ret = os.system('../examples/connection_tester gen-data -t checking_benchmark.torrent -p .') + if ret != 0: + print('ERROR: connection_tester failed: %d' % ret) + sys.exit(1) + + for threads in [4, 8, 16, 32, 64]: + run_test('%d' % threads, '--hashing_threads=%d' % threads) + + +def run_test(name, client_arg): + output_dir = 'logs_checking_%s' % name + + timing_path = os.path.join(output_dir, 'timing.txt') + if os.path.exists(timing_path): + print('file "{path}" exists, skipping test "{name}"'.format(path=timing_path, name=name)) + return + + rm_file_or_dir(output_dir) + try: + os.mkdir(output_dir) + except Exception: + pass + + rm_file_or_dir(".resume") + + client_cmd = ('../examples/client_test checking_benchmark.torrent ' + '--enable_dht=0 --enable_lsd=0 --enable_upnp=0 --enable_natpmp=0 ' + '-1 %s -s . -f %s/events.log --alert_mask=all' + ) % (client_arg, output_dir) + + client_out = open('%s/client.out' % output_dir, 'w+') + print('client_cmd: "{cmd}"'.format(cmd=client_cmd)) + c = subprocess.Popen(client_cmd.split(' '), stdout=client_out, stderr=client_out, stdin=subprocess.PIPE) + start_time = time.time() + + if platform.system() == "Linux": + out = {} + while c.returncode is None: + capture_sample(c.pid, start_time, out) + time.sleep(0.1) + c.poll() + + stats_filename = f"{output_dir}/memory_stats.log" + keys = print_output_to_file(out, stats_filename) + plot_output(stats_filename, keys) + else: + c.wait() + + client_out.close() + + start_time = 0 + end_time = 0 + for l in open('%s/events.log' % output_dir, 'r'): + if 'checking_benchmark: start_checking, m_checking_piece: ' in l \ + and start_time == 0: + start_time = int(l.split(' ')[0][1:-1]) + if 'state changed to: finished' in l \ + and start_time != 0: + end_time = int(l.split(' ')[0][1:-1]) + + print('%s: %d' % (name, end_time - start_time)) + with open('%s/timing.txt' % output_dir, 'w+') as f: + f.write('%s: %d\n' % (name, end_time - start_time)) + + +def rm_file_or_dir(path): + """ Attempt to remove file or directory at path + """ + try: + shutil.rmtree(path) + except Exception: + pass + + try: + os.remove(path) + except Exception: + pass + + +def parse_args(): + p = argparse.ArgumentParser() + p.add_argument('--toolset', default="") + + return p.parse_args() + + +if __name__ == '__main__': + main() diff --git a/tools/cibuildwheel/manylinux/build-openssl.sh b/tools/cibuildwheel/manylinux/build-openssl.sh new file mode 100755 index 0000000..e39fbf0 --- /dev/null +++ b/tools/cibuildwheel/manylinux/build-openssl.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Top-level build script called from Dockerfile + +# Stop at any error, show all commands +set -exuo pipefail + +# Get script directory +MY_DIR=$(dirname "${BASH_SOURCE[0]}") + +# Get build utilities +source $MY_DIR/build_utils.sh + +# Install a more recent openssl +check_var ${OPENSSL_ROOT} +check_var ${OPENSSL_HASH} +check_var ${OPENSSL_DOWNLOAD_URL} + +OPENSSL_VERSION=${OPENSSL_ROOT#*-} +OPENSSL_MIN_VERSION=1.1.1 + +INSTALLED=$(openssl version | head -1 | awk '{ print $2 }') +SMALLEST=$(echo -e "${INSTALLED}\n${OPENSSL_MIN_VERSION}" | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n | head -1) +if [ "${SMALLEST}" == "${OPENSSL_MIN_VERSION}" ]; then + echo "skipping installation of openssl ${OPENSSL_VERSION}, system provides openssl ${INSTALLED} which is newer than openssl ${OPENSSL_MIN_VERSION}" + exit 0 +fi + +if which yum; then + yum erase -y openssl-devel +else + apt-get remove -y libssl-dev +fi + +fetch_source ${OPENSSL_ROOT}.tar.gz ${OPENSSL_DOWNLOAD_URL} +check_sha256sum ${OPENSSL_ROOT}.tar.gz ${OPENSSL_HASH} +tar -xzf ${OPENSSL_ROOT}.tar.gz +pushd ${OPENSSL_ROOT} +./config no-shared --prefix=/usr/local/ssl --openssldir=/usr/local/ssl CPPFLAGS="${MANYLINUX_CPPFLAGS}" CFLAGS="${MANYLINUX_CFLAGS} -fPIC" CXXFLAGS="${MANYLINUX_CXXFLAGS} -fPIC" LDFLAGS="${MANYLINUX_LDFLAGS} -fPIC" > /dev/null +make > /dev/null +make install_sw > /dev/null +popd +rm -rf ${OPENSSL_ROOT} ${OPENSSL_ROOT}.tar.gz + + +/usr/local/ssl/bin/openssl version diff --git a/tools/cibuildwheel/manylinux/build_utils.sh b/tools/cibuildwheel/manylinux/build_utils.sh new file mode 100755 index 0000000..45f5931 --- /dev/null +++ b/tools/cibuildwheel/manylinux/build_utils.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Helper utilities for build + + +# use all flags used by ubuntu 20.04 for hardening builds, dpkg-buildflags --export +# other flags mentioned in https://wiki.ubuntu.com/ToolChain/CompilerFlags can't be +# used because the distros used here are too old +MANYLINUX_CPPFLAGS="-Wdate-time -D_FORTIFY_SOURCE=2" +MANYLINUX_CFLAGS="-g -O2 -Wall -fdebug-prefix-map=/=. -fstack-protector-strong -Wformat -Werror=format-security" +MANYLINUX_CXXFLAGS="-g -O2 -Wall -fdebug-prefix-map=/=. -fstack-protector-strong -Wformat -Werror=format-security" +MANYLINUX_LDFLAGS="-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now" + + +function check_var { + if [ -z "$1" ]; then + echo "required variable not defined" + exit 1 + fi +} + + +function fetch_source { + # This is called both inside and outside the build context (e.g. in Travis) to prefetch + # source tarballs, where curl exists (and works) + local file=$1 + check_var ${file} + local url=$2 + check_var ${url} + if [ -f ${file} ]; then + echo "${file} exists, skipping fetch" + else + curl -fsSL -o ${file} ${url}/${file} + fi +} + + +function check_sha256sum { + local fname=$1 + check_var ${fname} + local sha256=$2 + check_var ${sha256} + + echo "${sha256} ${fname}" > ${fname}.sha256 + sha256sum -c ${fname}.sha256 + rm -f ${fname}.sha256 +} + + +function do_standard_install { + ./configure "$@" CPPFLAGS="${MANYLINUX_CPPFLAGS}" CFLAGS="${MANYLINUX_CFLAGS}" "CXXFLAGS=${MANYLINUX_CXXFLAGS}" LDFLAGS="${MANYLINUX_LDFLAGS}" > /dev/null + make > /dev/null + make install > /dev/null +} + +function strip_ { + # Strip what we can -- and ignore errors, because this just attempts to strip + # *everything*, including non-ELF files: + find $1 -type f -print0 | xargs -0 -n1 strip --strip-unneeded 2>/dev/null || true +} + +function clean_pyc { + find $1 -type f -a \( -name '*.pyc' -o -name '*.pyo' \) -delete +} diff --git a/tools/cibuildwheel/manylinux/openssl-version.sh b/tools/cibuildwheel/manylinux/openssl-version.sh new file mode 100755 index 0000000..8c645e1 --- /dev/null +++ b/tools/cibuildwheel/manylinux/openssl-version.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +export OPENSSL_ROOT=openssl-1.1.1l +export OPENSSL_HASH=0b7a3e5e59c34827fe0c3a74b7ec8baef302b98fa80088d7f9153aa16fa76bd1 +export OPENSSL_DOWNLOAD_URL=https://www.openssl.org/source diff --git a/tools/cibuildwheel/setup_boost.sh b/tools/cibuildwheel/setup_boost.sh new file mode 100755 index 0000000..c369d01 --- /dev/null +++ b/tools/cibuildwheel/setup_boost.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# This script is meant to be called by cibuildwheel. It should run on github +# actions Linux, Mac and Windows. + +set -ex + +VERSION="$1" +BOOST_ROOT="$2" + +VUNDER="${VERSION//./_}" + +TEMP_ARCHIVE="$(mktemp)" + +mkdir -p "$BOOST_ROOT" + +curl -L -o "$TEMP_ARCHIVE" "https://boostorg.jfrog.io/artifactory/main/release/${VERSION}/source/boost_${VUNDER}.tar.gz" + +tar -z -x -C "$BOOST_ROOT" -f "$TEMP_ARCHIVE" --strip-components 1 +rm "$TEMP_ARCHIVE" + +cd "$BOOST_ROOT" + +./bootstrap.sh + +cat ./project-config.jam + +./b2 headers diff --git a/tools/cibuildwheel/setup_ccache_on_manylinux.sh b/tools/cibuildwheel/setup_ccache_on_manylinux.sh new file mode 100755 index 0000000..e7f9e4f --- /dev/null +++ b/tools/cibuildwheel/setup_ccache_on_manylinux.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +set -xe + +if [ $(uname -m) != x86_64 -a $(uname -m) != aarch64 ] +then + echo "ccache isn't known to exist on $(uname -m). skipping ccache setup" + exit 0 +fi + +yum install -y epel-release # overlay containing ccache +yum install -y ccache + +# The symlinks in /usr/lib64/ccache are auto-managed by rpm postinstall +# scripts. They are only created if appropriate packages are installed. However +# this management only knows about the standard gcc* packages, not the +# devtoolset packages, so they don't get created correctly on manylinux. We try +# to create them ourselves. +mkdir -p /usr/local/ccache/bin +for path in /opt/rh/devtoolset-*/root/usr/bin/*cc /opt/rh/devtoolset-*/root/usr/bin/*cc-[0-9]* /opt/rh/devtoolset-*/root/usr/bin/*++ /opt/rh/devtoolset-*/root/usr/bin/*++-[0-9]* /opt/rh/devtoolset-*/root/usr/bin/*cpp /opt/rh/devtoolset-*/root/usr/bin/*cpp-[0-9]* +do + ln -s /usr/bin/ccache "/usr/local/ccache/bin/$(basename "$path")" +done diff --git a/tools/cibuildwheel/setup_openssl.sh b/tools/cibuildwheel/setup_openssl.sh new file mode 100755 index 0000000..dcd19d8 --- /dev/null +++ b/tools/cibuildwheel/setup_openssl.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +set -xe + +TOOLS=$(dirname "$(readlink -f "$0")") + +. "$TOOLS/manylinux/openssl-version.sh" +manylinux-entrypoint "$TOOLS/manylinux/build-openssl.sh" + +# If the build script finds a new enough openssl on the system, it will skip building. + +if [ -d /usr/local/ssl ] +then + ln -s /usr/local/ssl/include/openssl /usr/local/include + ln -s /usr/local/ssl/lib/libcrypto.a /usr/local/lib + ln -s /usr/local/ssl/lib/libssl.a /usr/local/lib +fi diff --git a/tools/clean.py b/tools/clean.py new file mode 100755 index 0000000..1a6560e --- /dev/null +++ b/tools/clean.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +import os +import shutil +import glob + + +def clean(): + to_delete = [ + 'session_stats', + 'libtorrent_logs*', + 'round_trip_ms.log', + 'dht.log', + 'upnp.log', + 'natpmp.log', + 'bin', + 'build-aux', + '.deps', + 'test_tmp_*', + 'bjam_build.*.xml', + '*.exe', + '*.pdb', + '*.pyd', + 'dist', + 'build', + '.libs', + '*.cpp.orig', + '*.cpp.rej', + '*.hpp.orig', + '*.hpp.rej', + '*.gcov', + '*.gcno', + '*.gcda', + 'lib*.a', + 'Jamfile.rej', + 'Jamfile.orig', + '*.o', + '*.lo', + 'autom4te.cache', + 'configure', + 'config.report', + 'config.log', + '.lib', + 'CMakeFiles', + 'CMakeCache.txt', + 'checking_benchmark', + 'cpu_benchmark', + ] + + directories = [ + 'examples', + 'test', + '.', + 'tools', + 'src', + 'simulation', + 'fuzzers', + os.path.join('src', 'kademlia'), + os.path.join('include', 'libtorrent'), + os.path.join('include', os.path.join('libtorrent', '_aux')), + os.path.join('include', os.path.join('libtorrent', 'kademlia')), + os.path.join('bindings', 'python'), + os.path.join('bindings', os.path.join('python', 'src')), + os.path.join('bindings', 'c'), + os.path.join('bindings', os.path.join('c', 'src')), + os.path.join('simulation', 'libsimulator') + ] + + for d in directories: + for f in to_delete: + path = os.path.join(d, f) + entries = glob.glob(path) + for p in entries: + try: + shutil.rmtree(p) + print(p) + except Exception as e: + print(p, e) + try: + os.remove(p) + print(p) + except Exception as e: + print(p, e) + + +if __name__ == "__main__": + clean() diff --git a/tools/copyright.py b/tools/copyright.py new file mode 100644 index 0000000..5103c8b --- /dev/null +++ b/tools/copyright.py @@ -0,0 +1,137 @@ +#!/usr/bin/python + +# essentially copy pasted from http://0pointer.de/blog/projects/copyright.html + +from subprocess import Popen, PIPE +from datetime import datetime + + +def pretty_years(s): + + li = list(s) + li.sort() + + start = None + prev = None + r = [] + + for x in li: + if prev is None: + start = x + prev = x + continue + + if x == prev + 1: + prev = x + continue + + if prev == start: + r.append("%i" % prev) + else: + r.append("%i-%i" % (start, prev)) + + start = x + prev = x + + if prev is not None: + if prev == start: + r.append("%i" % prev) + else: + r.append("%i-%i" % (start, prev)) + + return ", ".join(r) + + +def order_by_year(a, b): + + la = list(a[2]) + la.sort() + + lb = list(b[2]) + lb.sort() + + if la[0] < lb[0]: + return -1 + elif la[0] > lb[0]: + return 1 + else: + return 0 + + +author_map = { + 'arvidn': 'Arvid Norberg', + 'pavel.pimenov': 'Pavel Pimenov', + 'd_komarov': 'd-komarov', + 'Chocobo1': 'Mike Tzou', + 'unsh': 'Un Shyam', + 'toinetoine': 'Antoine Dahan' +} + + +def map_author(a): + if a in author_map: + return author_map[a] + else: + return a + + +def get_authors(f): + + print("File: %s" % f) + + commits = [] + data = {} + + for ln in Popen(["git", "blame", "--incremental", f], stdout=PIPE).stdout: + + if ln.startswith("filename "): + if len(data) > 0: + commits.append(data) + data = {} + + elif ln.startswith("author "): + data["author"] = map_author(ln[7:].strip()) + + elif ln.startswith("author-mail <"): + data["author-mail"] = ln[12:].strip() + + elif ln.startswith("author-time "): + data["author-time"] = ln[11:].strip() + + elif ln.startswith("author-tz "): + data["author-tz"] = ln[9:].strip() + + by_author = {} + + for c in commits: + try: + if c['author'] == 'Not Committed Yet': + continue + n = by_author[c["author"]] + except KeyError: + n = (c["author"], c["author-mail"], set()) + by_author[c["author"]] = n + + # FIXME: Handle time zones properly + year = datetime.fromtimestamp(int(c["author-time"])).year + + n[2].add(year) + + for an, a in list(by_author.iteritems()): + for bn, b in list(by_author.iteritems()): + if a is b: + continue + + if a[1] == b[1]: + a[2].update(b[2]) + + if an in by_author and bn in by_author: + del by_author[bn] + + copyright = list(by_author.itervalues()) + copyright.sort(order_by_year) + + ret = '' + for name, mail, years in copyright: + ret += "Copyright (c) %s, %s\n" % (pretty_years(years), name) + return ret diff --git a/tools/dht_flood.py b/tools/dht_flood.py new file mode 100755 index 0000000..8f82440 --- /dev/null +++ b/tools/dht_flood.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +import socket +import sys +from types import StringType, IntType, LongType, DictType, ListType, TupleType +import random + +port = int(sys.argv[1]) + +# from BitTorrent 4.3.0 + + +def encode_bencached(x, r): + r.append(x.bencoded) + + +def encode_int(x, r): + r.extend(('i', str(x), 'e')) + + +def encode_string(x, r): + r.extend((str(len(x)), ':', x)) + + +def encode_list(x, r): + r.append('l') + for i in x: + encode_func[type(i)](i, r) + r.append('e') + + +def encode_dict(x, r): + r.append('d') + ilist = sorted(x.items()) + for k, v in ilist: + r.extend((str(len(k)), ':', k)) + encode_func[type(v)](v, r) + r.append('e') + + +encode_func = {} +encode_func[IntType] = encode_int +encode_func[LongType] = encode_int +encode_func[StringType] = encode_string +encode_func[ListType] = encode_list +encode_func[TupleType] = encode_list +encode_func[DictType] = encode_dict + + +def bencode(x): + r = [] + encode_func[type(x)](x, r) + return ''.join(r) + + +def send_dht_message(msg): + s.sendto(bencode(msg), 0, ('127.0.0.1', port)) + + +def random_key(): + ret = '' + for i in range(0, 20): + ret += chr(random.randint(0, 255)) + return ret + + +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +node_id = '1' * 20 +query = 'get_peers' + +print('test random info-hashes') +for i in range(1, 30000): + send_dht_message({'a': {'id': node_id, 'info_hash': random_key()}, 'q': query, 'y': 'q', 't': '%d' % i}) + +print('test random peer-ids') +for i in range(1, 30000): + send_dht_message({'a': {'id': random_key(), 'info_hash': random_key()}, 'q': query, 'y': 'q', 't': '%d' % i}) diff --git a/tools/dht_put.cpp b/tools/dht_put.cpp new file mode 100644 index 0000000..c1fb117 --- /dev/null +++ b/tools/dht_put.cpp @@ -0,0 +1,430 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016, Alden Torres +Copyright (c) 2016, Steven Siloti +Copyright (c) 2019, Amir Abrams +Copyright (c) 2020, FranciscoPombal +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 "libtorrent/session_params.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 + +namespace { + +bool log_pkts = false; +bool log_dht = false; + +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 += static_cast(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 = static_cast((t1 << 4) | (t2 & 0xf)); + } + return true; +} + +[[noreturn]] void usage() +{ + std::fprintf(stderr, + "USAGE:\ndht [options] \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" + "\n" + "OPTIONS:\n" + "--log-packets print DHT messages as they are sent and received\n" + "--log-dht print DHT log messages\n" + ); + exit(1); +} + +alert* wait_for_alert(lt::session& s, int alert_type) +{ + alert* ret = nullptr; + while (!ret) + { + s.wait_for_alert(seconds(5)); + + std::vector alerts; + s.pop_alerts(&alerts); + for (auto const a : alerts) + { + if (!log_pkts && !log_dht) + { + static int spinner = 0; + static const char anim[] = {'-', '\\', '|', '/'}; + std::printf("\r%c", anim[spinner]); + std::fflush(stdout); + spinner = (spinner + 1) & 3; + } + if (a->type() == dht_pkt_alert::alert_type && log_pkts) + std::printf("%s\n", a->message().c_str()); + else if (a->type() == dht_log_alert::alert_type && log_dht) + std::printf("%s\n", a->message().c_str()); + else if (a->type() == dht_error_alert::alert_type) + std::printf("%s\n", a->message().c_str()); + if (a->type() != alert_type) continue; + ret = a; + } + } + std::printf("\r"); + 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; +} + +lt::session_params load_dht_state() +{ + std::fstream f(".dht", std::ios_base::in | std::ios_base::binary); + f.unsetf(std::ios_base::skipws); + std::printf("load dht state from .dht\n"); + std::vector const state(std::istream_iterator{f} + , std::istream_iterator{}); + + if (f.bad() || state.empty()) + { + std::fprintf(stderr, "failed to read .dht\n"); + return {}; + } + return read_session_params(state); +} + +} // anonymous namespace + +int main(int argc, char* argv[]) +{ + // skip pointer to self + ++argv; + --argc; + + if (argc < 1) usage(); + + while (argc > 1) + { + lt::string_view const option(argv[0]); + if (option.substr(0, 2) != "--"_sv) + break; + + if (option == "--log-packets"_sv) + log_pkts = true; + else if (option == "--log-dht"_sv) + log_dht = true; + + ++argv; + --argc; + } + + 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]); + } + + session_params sp = load_dht_state(); + sp.settings.set_bool(settings_pack::enable_dht, true); + sp.settings.set_int(settings_pack::alert_mask, 0x7fffffff); + lt::session s(sp); + + 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(); + } + + std::vector state = write_session_params_buf(s.session_state(session::save_dht_state)); + 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; +} + +#endif diff --git a/tools/dht_sample.cpp b/tools/dht_sample.cpp new file mode 100644 index 0000000..30b5ec6 --- /dev/null +++ b/tools/dht_sample.cpp @@ -0,0 +1,201 @@ +/* + +Copyright (c) 2019-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/session.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/random.hpp" + +#include +#include +#include +#include +#include +#include +#include + +using namespace lt; +using namespace lt::dht; +using namespace std::placeholders; + +using namespace std::literals::chrono_literals; + +lt::clock_type::duration const min_request_interval = 5min; + +#ifdef TORRENT_DISABLE_DHT + +int main(int, char*[]) +{ + std::cerr << "not built with DHT support\n"; + return 1; +} + +#else + +namespace { + +std::atomic_bool quit(false); + +void stop(int) { quit = true; } + +[[noreturn]] void usage() +{ + std::cerr << "USAGE: dht-sample\n"; + exit(1); +} + +lt::session_params load_dht_state() +{ + std::fstream f(".dht", std::ios_base::in | std::ios_base::binary); + f.unsetf(std::ios_base::skipws); + std::cout << "load dht state from .dht\n"; + std::vector const state(std::istream_iterator{f} + , std::istream_iterator{}); + + if (f.bad()) + { + std::cerr << "failed to read .dht\n"; + return {}; + } + return read_session_params(state); +} + +struct node_entry +{ + lt::time_point next_request = lt::min_time(); + lt::time_point last_seen = lt::clock_type::now(); +}; + +} // anonymous namespace + +int main(int argc, char*[]) +{ + if (argc != 1) usage(); + + signal(SIGINT, &stop); + signal(SIGTERM, &stop); + + session_params sp = load_dht_state(); + sp.settings.set_bool(settings_pack::enable_dht, true); + sp.settings.set_int(settings_pack::alert_mask, 0x7fffffff); + lt::session s(sp); + + lt::time_point next_send = lt::clock_type::now() + 5s; + lt::time_point next_prune = lt::clock_type::now() + 30min; + std::map nodes; + std::set info_hashes; + + while (!quit) + { + s.wait_for_alert(5s); + + std::vector alerts; + s.pop_alerts(&alerts); + auto const now = lt::clock_type::now(); + for (alert* a : alerts) + { + if (auto* sa = lt::alert_cast(a)) + { + + for (auto const& ih : sa->samples()) + { + if (info_hashes.insert(ih).second) + std::cout << ih << '\n'; + } + for (auto const& n : sa->nodes()) + { + auto it = nodes.find(n.second); + if (it == nodes.end()) + it = nodes.insert({n.second, {}}).first; + else + it->second.last_seen = now; + it->second.next_request = now + std::max(sa->interval + , min_request_interval); + } + std::cout.flush(); + } + else if (auto* dp = alert_cast(a)) + { + auto it = nodes.find(dp->node); + if (it == nodes.end()) + nodes.insert({dp->node, {}}); + else + it->second.last_seen = now; + } + else if (auto* aa = alert_cast(a)) + { + if (info_hashes.insert(aa->info_hash).second) + std::cout << aa->info_hash << std::endl; + } + } + + if (now > next_send) + { + next_send = now + 1s; + auto const it = std::find_if(nodes.begin(), nodes.end() + , [now](std::pair const& n) + { return n.second.next_request < now; }); + if (it != nodes.end()) + { + // just push this forward. If we get a response, this will be + // updated with the interval announced by the node + it->second.next_request = now + 1h; + sha1_hash target; + for (auto& b : target) b = std::uint8_t(std::rand()); + s.dht_sample_infohashes(it->first, target); + } + } + + if (now > next_prune) + { + next_prune = now + 30min; + + // remove any node that we haven't seen in 6 hours + for (auto it = nodes.begin(); it != nodes.end();) + { + if (it->second.last_seen + 6h < now) + it = nodes.erase(it); + else + ++it; + } + } + } + + std::vector const state = write_session_params_buf(s.session_state(session::save_dht_state)); + 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; +} + +#endif diff --git a/tools/disk_io_stress_test.cpp b/tools/disk_io_stress_test.cpp new file mode 100644 index 0000000..3109dfd --- /dev/null +++ b/tools/disk_io_stress_test.cpp @@ -0,0 +1,317 @@ +/* + +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/session.hpp" // for default_disk_io_constructor +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/add_torrent_params.hpp" + +// TODO: remove this dependency +#include "libtorrent/aux_/path.hpp" + +#include +#include +#include +#include + +using disk_test_mode_t = lt::flags::bitfield_flag; + +using lt::operator""_bit; +using lt::operator "" _sv; + +namespace test_mode { +constexpr disk_test_mode_t sparse = 0_bit; +constexpr disk_test_mode_t even_file_sizes = 1_bit; +constexpr disk_test_mode_t read_random_order = 2_bit; +constexpr disk_test_mode_t flush_files = 3_bit; +} + +std::mt19937 random_engine(std::random_device{}()); + +// TODO: in C++17, use std::filesystem +void remove_all(std::string path) +{ +#ifdef TORRENT_WINDOWS + WIN32_FIND_DATA data; + HANDLE list = ::FindFirstFile(path.c_str(), &data); + if (list == INVALID_HANDLE_VALUE) + { + ::DeleteFile(path.c_str()); + return; + } + + do + { + if (data.cFileName != "."_sv && data.cFileName != ".."_sv) + { + remove_all(path + "\\" + data.cFileName); + } + } while(FindNextFile(list, &data)); + FindClose(list); + RemoveDirectory(path.c_str()); +#else + DIR* handle = ::opendir(path.c_str()); + if (handle == nullptr) + { + ::remove(path.c_str()); + return; + } + + dirent* de = ::readdir(handle); + while (de != nullptr) + { + if (de->d_name != "."_sv && de->d_name != ".."_sv) + { + remove_all(path + "/" + de->d_name); + } + de = ::readdir(handle); + } + ::closedir(handle); + ::remove(path.c_str()); +#endif +} + +int run_test(disk_test_mode_t const flags + , int const num_threads + , int const file_pool_size + , int const num_files + , int const queue_limit + , int const read_multiplier) try +{ + lt::io_context ioc; + lt::counters cnt; + lt::settings_pack pack; + pack.set_int(lt::settings_pack::aio_threads, num_threads); + pack.set_int(lt::settings_pack::file_pool_size, file_pool_size); + + std::unique_ptr disk_io + = lt::default_disk_io_constructor(ioc, pack, cnt); + + lt::file_storage fs; + + std::int64_t file_size = (flags & test_mode::even_file_sizes) + ? 0x1000 + : 1337; + for (int i = 0; i < num_files; ++i) + { + fs.add_file("test/" + std::to_string(i), file_size); + file_size *= 2; + } + + std::int64_t const total_size = fs.total_size(); + int const piece_size = 0x8000; + int const blocks_per_piece = std::max(1, piece_size / lt::default_block_size); + int const num_pieces = static_cast((total_size + piece_size - 1) / piece_size); + fs.set_num_pieces(num_pieces); + fs.set_piece_length(piece_size); + + std::cerr << "RUNNING: " + << ((flags & test_mode::sparse) ? "s-" : "f-") + << ((flags & test_mode::even_file_sizes) ? "e-" : "o-") + << ((flags & test_mode::read_random_order) ? "rr-" : "or-") + << ((flags & test_mode::flush_files) ? "f-" : "a-") + << num_pieces << '-' + << file_pool_size << '-' + << queue_limit << '-' + << read_multiplier + << ": "; + + // TODO: in C++17, use std::filesystem + remove_all("scratch-area"); + + // TODO: add test mode where some file priorities are 0 + + lt::aux::vector prios; + std::string save_path = "./scratch-area"; + lt::storage_params params(fs, nullptr + , save_path + , (flags & test_mode::sparse) ? lt::storage_mode_sparse : lt::storage_mode_allocate + , prios + , lt::sha1_hash("01234567890123456789")); + + lt::storage_holder t = disk_io->new_torrent(params, {}); + + std::vector blocks_to_write; + for (int p = 0; p < num_pieces; ++p) + { + for (int b = 0; b < blocks_per_piece; ++b) + { + blocks_to_write.push_back( + {lt::piece_index_t{p}, b * lt::default_block_size, lt::default_block_size}); + } + } + std::shuffle(blocks_to_write.begin(), blocks_to_write.end(), random_engine); + + std::vector blocks_to_read; + blocks_to_read.reserve(blocks_to_write.size()); + + std::vector write_buffer(lt::default_block_size); + + int outstanding = 0; + + lt::add_torrent_params atp; + + disk_io->async_check_files(t, &atp, lt::aux::vector{} + , [&](lt::status_t, lt::storage_error const&) { --outstanding; }); + ++outstanding; + disk_io->submit_jobs(); + + while (outstanding > 0) + { + ioc.run_one(); + ioc.restart(); + } + + int job_counter = 0; + + while (!blocks_to_write.empty() + || !blocks_to_read.empty() + || outstanding > 0) + { + for (int i = 0; i < read_multiplier; ++i) + { + if (!blocks_to_read.empty() && outstanding < queue_limit) + { + auto const req = blocks_to_read.back(); + blocks_to_read.erase(blocks_to_read.end() - 1); + + disk_io->async_read(t, req + , [&](lt::disk_buffer_holder h, lt::storage_error const& ec) + { + TORRENT_UNUSED(h); + --outstanding; + ++job_counter; + if (ec) throw std::runtime_error("async_read failed " + ec.ec.message()); + // TODO: validate that we read the correct data. buffer + // in h + }); + + ++outstanding; + } + } + + if (!blocks_to_write.empty() && outstanding < queue_limit) + { + auto const req = blocks_to_write.back(); + blocks_to_write.erase(blocks_to_write.end() - 1); + + // TODO: put a pattern in write_buffer that can be validated in read + // operations + disk_io->async_write(t, req, write_buffer.data() + , {}, [&,req](lt::storage_error const& ec) + { + --outstanding; + ++job_counter; + if (ec) throw std::runtime_error("async_write failed " + ec.ec.message()); + if (flags & test_mode::read_random_order) + { + std::uniform_int_distribution<> d(0, int(blocks_to_read.size())); + blocks_to_read.insert(blocks_to_read.begin() + d(random_engine), req); + } + else + { + blocks_to_read.push_back(req); + } + // if read_multiplier > 1, put this block more times in the + // read queue + for (int i = 1; i < read_multiplier; ++i) + { + std::uniform_int_distribution<> d(0, int(blocks_to_read.size())); + blocks_to_read.insert(blocks_to_read.begin() + d(random_engine), req); + } + }); + + ++outstanding; + } + + if ((flags & test_mode::flush_files) && (job_counter % 500) == 499) + { + disk_io->async_release_files(t, [&]() + { + --outstanding; + ++job_counter; + }); + ++outstanding; + } + + // TODO: add test_mode for async_move_storage + // TODO: add test_mode for async_hash and async_hash2 + // TODO: add test_mode for abort_hash_jobs + // TODO: add test_mode for async_delete_files + // TODO: add test_mode for async_rename_file + // TODO: add test_mode for async_set_file_priority + + disk_io->submit_jobs(); + if (outstanding >= queue_limit) + ioc.run_one(); + else + ioc.poll(); + ioc.restart(); + } + + disk_io->remove_torrent(t); + + disk_io->abort(true); + + std::cerr << "OK\n"; + return 0; +} +catch (std::exception const& e) +{ + std::cerr << "FAILED WITH EXCEPTION: " << e.what() << '\n'; + return 1; +} + +int main(int, char const*[]) +{ + // TODO: make it possible to run a test with all custom arguments from the + // command line + + int num_files = 20; + int queue_size = 32; + int num_threads = 16; + int read_multiplier = 3; + int file_pool_size = 10; + + int ret = 0; + ret |= run_test(test_mode::sparse, num_threads, file_pool_size, num_files, queue_size, read_multiplier); + ret |= run_test(test_mode::sparse | test_mode::even_file_sizes, num_threads, file_pool_size, num_files, queue_size, read_multiplier); + ret |= run_test(test_mode::read_random_order | test_mode::sparse, num_threads, file_pool_size, num_files, queue_size, read_multiplier); + ret |= run_test(test_mode::read_random_order | test_mode::sparse | test_mode::even_file_sizes, num_threads, file_pool_size, num_files, queue_size, read_multiplier); + ret |= run_test(test_mode::flush_files | test_mode::read_random_order | test_mode::sparse | test_mode::even_file_sizes, num_threads, file_pool_size, num_files, queue_size, read_multiplier); + + return ret; +} + diff --git a/tools/gen_convenience_header.py b/tools/gen_convenience_header.py new file mode 100644 index 0000000..871bd8b --- /dev/null +++ b/tools/gen_convenience_header.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +import os +from pathlib import Path + +excludes = ['libtorrent.hpp', 'storage.hpp', 'io_service.hpp'] + +os.remove('include/libtorrent/libtorrent.hpp') +with open('include/libtorrent/libtorrent.hpp', 'w+') as f: + f.write(''' +// This header is generated by tools/gen_convenience_header.py + +''') + + for fn in os.popen('git ls-files include/libtorrent/*.hpp include/libtorrent/kademlia/*.hpp include/libtorrent/extensions/*.hpp'): + + fn = Path(fn.strip()) + + if fn.name in excludes: continue + + f.write('#include "%s"\n' % fn.relative_to('include')) diff --git a/tools/gen_fwd.py b/tools/gen_fwd.py new file mode 100644 index 0000000..5f244da --- /dev/null +++ b/tools/gen_fwd.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +import os + +file_header = '''/* + +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2017-2021, Arvid Norberg +Copyright (c) 2020, 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_FWD_HPP +#define TORRENT_FWD_HPP + +#include "libtorrent/config.hpp" + +namespace libtorrent { +''' + +file_footer = ''' + +} + +namespace lt = libtorrent; + +#endif // TORRENT_FWD_HPP +''' + +classes = os.popen( + r'git grep "\(TORRENT_EXPORT\|TORRENT_DEPRECATED_EXPORT\|^TORRENT_[A-Z0-9]\+_NAMESPACE\)"').read().split('\n') + + +def print_classes(out, classes, keyword): + current_file = '' + + # [(file, decl), ...] + classes = [(x.split(':')[0].strip(), ':'.join(x.split(':')[1:]).strip()) for x in classes] + + # we only care about header files + # ignore the forward header itself, that's the one we're generating + # also ignore any header in the aux_ directory, those are private + classes = [x for x in classes if x[0].endswith('.hpp') and not x[0].endswith('/fwd.hpp') and '/aux_/' not in x[0]] + + namespaces = ['TORRENT_VERSION_NAMESPACE_3', + 'TORRENT_VERSION_NAMESPACE_3_END', + 'TORRENT_VERSION_NAMESPACE_2', + 'TORRENT_VERSION_NAMESPACE_2_END', + 'TORRENT_CRYPTO_NAMESPACE', + 'TORRENT_CRYPTO_NAMESPACE_END'] + + # only include classes with the right kind of export + classes = [ + x for x in classes if x[1] in namespaces or ( + x[1].split(' ')[0] in [ + 'class', + 'struct'] and x[1].split(' ')[1] == keyword)] + + # collapse empty namespaces + classes2 = [] + skip = 0 + for i in range(len(classes)): + if skip > 0: + skip -= 1 + continue + if classes[i][1] in namespaces \ + and len(classes) > i + 1 \ + and classes[i + 1][1] == ('%s_END' % classes[i][1]): + skip = 1 + else: + classes2.append(classes[i]) + + classes = classes2 + + idx = -1 + for line in classes: + idx += 1 + this_file = line[0] + decl = line[1].split(' ') + + content = '' + if this_file != current_file: + out.write('\n// ' + this_file + '\n') + current_file = this_file + if len(decl) > 2 and decl[0] in ['struct', 'class']: + decl = decl[0] + ' ' + decl[2] + if not decl.endswith(';'): + decl += ';' + content = decl + '\n' + else: + content = line[1] + '\n' + + if 'kademlia' in this_file: + out.write('namespace dht {\n') + out.write(content) + out.write('}\n') + else: + out.write(content) + + +os.remove('include/libtorrent/fwd.hpp') +with open('include/libtorrent/fwd.hpp', 'w+') as f: + f.write(file_header) + + print_classes(f, classes, 'TORRENT_EXPORT') + + f.write('\n#if TORRENT_ABI_VERSION <= 2\n') + + print_classes(f, classes, 'TORRENT_DEPRECATED_EXPORT') + + f.write('\n#endif // TORRENT_ABI_VERSION') + + f.write(file_footer) diff --git a/tools/libtorrent_lldb.py b/tools/libtorrent_lldb.py new file mode 100644 index 0000000..3770685 --- /dev/null +++ b/tools/libtorrent_lldb.py @@ -0,0 +1,184 @@ +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +import lldb +import struct + +# this is an LLDB pretty printer for libtorrent types. To use in LLDB run: +# +# command script import tools/libtorrent_lldb.py +# +# or add it to your ~/.lldbinit + +def __lldb_init_module (debugger, dict): + debugger.HandleCommand("type summary add -x \"^libtorrent::digest32<.+>$\" -F " + + "libtorrent_lldb.print_hash -p -w libtorrent") + debugger.HandleCommand("type summary add -x \"^libtorrent::sha256_hash$\" -F " + + "libtorrent_lldb.print_hash -p -w libtorrent") + debugger.HandleCommand("type summary add -x \"^libtorrent::sha1_hash$\" -F " + + "libtorrent_lldb.print_hash -p -w libtorrent") + debugger.HandleCommand("type summary add -x \"^libtorrent::span<.+>$\" -F " + + "libtorrent_lldb.print_span -p -w libtorrent") + debugger.HandleCommand("type summary add -x \"^libtorrent::flags::bitfield_flag<.+>$\" -F " + + "libtorrent_lldb.print_flag -p -w libtorrent") + debugger.HandleCommand("type summary add -x \"^boost::asio::ip::basic_endpoint<.+>$\" -F " + + "libtorrent_lldb.print_endpoint -p -w libtorrent") + debugger.HandleCommand("type summary add -x \"^libtorrent::bitfield$\" -F " + + "libtorrent_lldb.print_bitfield -p -w libtorrent") + debugger.HandleCommand("type summary add -x \"^libtorrent::typed_bitfield<.+>$\" -F " + + "libtorrent_lldb.print_bitfield -p -w libtorrent") + debugger.HandleCommand("type summary add -x \"^libtorrent::aux::strong_typedef<.+>$\" -F " + + "libtorrent_lldb.print_strong_type -p -w libtorrent") + debugger.HandleCommand("type category enable libtorrent") + +def print_hash(valobj, internal_dict): + + if valobj.GetType().IsReferenceType(): + valobj = valobj.Dereference() + + data = valobj.GetChildMemberWithName("m_number").GetData().uint8s + return bytes(data).hex() + +def print_flag(valobj, internal_dict): + + if valobj.GetType().IsReferenceType(): + valobj = valobj.Dereference() + + data = valobj.GetChildMemberWithName("m_val").GetValueAsUnsigned() + return "({}) {:b}".format(valobj.GetType().name, data) + +def swap16(i): + return struct.unpack("H", i))[0] + +def pairs(lst): + for i in range(0, len(lst), 2): + yield lst[i:i+2] + +def print_endpoint(valobj, internal_dict): + + if valobj.GetType().IsReferenceType(): + valobj = valobj.Dereference() + + union = valobj.GetChildMemberWithName("impl_").GetChildMemberWithName("data_") + family = union.GetChildMemberWithName("base").GetChildMemberWithName("sa_family").GetValueAsUnsigned() + + if family == 2: + a = union.GetChildMemberWithName("v4").GetChildMemberWithName("sin_addr").GetData().uint8s + addr = ".".join([f"{b}" for b in a ]) + p = swap16(union.GetChildMemberWithName("v4").GetChildMemberWithName("sin_port").GetValueAsUnsigned()) + return "{}:{}".format(addr, p) + else: + a = union.GetChildMemberWithName("v6").GetChildMemberWithName("sin6_addr").GetData().uint8s + p = swap16(union.GetChildMemberWithName("v6").GetChildMemberWithName("sin6_port").GetValueAsUnsigned()) + addr = ":".join(x+y for x, y in pairs(["{:02x}".format(b) for b in a])) + return "[{}]:{}".format(addr, p) + +def print_bitfield(valobj, internal_dict): + + if valobj.GetType().IsReferenceType(): + valobj = valobj.Dereference() + + array = valobj.GetChildMemberWithName("m_buf").GetChildMemberWithName("__ptr_").GetChildMemberWithName("__value_") + size = array.Dereference().GetValueAsUnsigned() + ret = "size: {} bits | ".format(size) + for idx in range((size + 31) // 32): + item = array.GetChildAtIndex(idx + 1, lldb.eNoDynamicValues, True) + buffer = item.GetData().uint8s + for b in buffer: + ret += "{:08b}".format(int(b)) + size -= 8 + + return ret + +def print_span(valobj, internal_dict): + + if valobj.GetType().IsReferenceType(): + valobj = valobj.Dereference() + + array = valobj.GetChildMemberWithName("m_ptr") + size = valobj.GetChildMemberWithName("m_len").GetValueAsSigned() + ret = "size = {}".format(size) + for idx in range(size): + if idx == 0: + item = array.Dereference() + else: + item = array.GetChildAtIndex(idx, lldb.eNoDynamicValues, True) + ret += "\n[{}] = {}".format(idx, item.summary) + return ret + + +def print_strong_type(valobj, internal_dict): + + if valobj.GetType().IsReferenceType(): + valobj = valobj.Dereference() + + name = valobj.GetType().name + data = valobj.GetChildMemberWithName("m_val").GetValue() + if "piece_index_tag" in name: + name = "piece_index" + elif "file_index_tag" in name: + name = "file_index" + elif "queue_position_tag" in name: + name = "queue_pos" + elif "piece_extent_tag" in name: + name = "piece_extent" + elif "storage_index_tag_t" in name: + name = "storage_index" + elif "disconnect_severity_tag" in name: + name = "disconnect_severity" + val = valobj.GetChildMemberWithName("m_val").GetValueAsUnsigned() + if val == 0: + data = "normal" + elif val == 1: + data = "failure" + elif val == 2: + data = "peer_error" + else: + data = " ({})".format(val) + elif "prio_index_tag_t" in name: + name = "prio_index" + elif "port_mapping_tag" in name: + name = "port_mapping" + elif "dl_queue_tag" in name or name == "libtorrent::download_queue_t": + name = "download_queue" + val = valobj.GetChildMemberWithName("m_val").GetValueAsUnsigned() + if val == 0: + data = "piece_downloading"; + elif val == 1: + data = "piece_full"; + elif val == 2: + data = "piece_finished"; + elif val == 3: + data = "piece_zero_prio"; + elif val == 4: + data = "piece_open"; + elif val == 5: + data = "piece_downloading_reverse"; + elif val == 6: + data = "piece_full_reverse"; + else: + data = " ({})".format(val) + elif "piece_extent_tag" in name: + name = "piece_extent" + elif "picker_options_tag" in name: + name = "picker_options" + val = valobj.GetChildMemberWithName("m_val").GetValueAsUnsigned() + flags = [] + if (val & 1) != 0: + flags.append("rarest_first") + if (val & 2) != 0: + flags.append("reverse") + if (val & 4) != 0: + flags.append("on_parole") + if (val & 8) != 0: + flags.append("prioritize_partials") + if (val & 16) != 0: + flags.append("sequential") + if (val & 64) != 0: + flags.append("align_expanded_pieces") + if (val & 128) != 0: + flags.append("piece_extent_affinity") + data = "|".join(flags) + else: + name = "" + + return "({}) {}".format(name, data) 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_lookup_log.py b/tools/parse_lookup_log.py new file mode 100755 index 0000000..129b7de --- /dev/null +++ b/tools/parse_lookup_log.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +# this is meant to parse the dht_lookups.log generated by parse_dht_log.py +from __future__ import print_function + +import os + +nodes = {} + + +def get_origin(n): + if n in nodes: + return list(nodes[n]['conns']) + else: + return ['0.0.0.0'] + + +def calculate_pos(nid, dist): + nid = int(nid[0:7], 16) + + x = 0 + y = 0 + for i in range(0, 28, 2): + x |= (nid & (1 << i)) >> (i / 2) + y |= (nid & (2 << i)) >> (i / 2 + 1) + +# print '%d -> %d %d' % (dist, x, y) + + return (x / 3, y / 3) + + +def plot_nodes(nodes, frame): + + try: + os.mkdir('dht_frames') + except Exception: + pass + + out = open('dht_frames/plot-%02d.dot' % frame, 'w+') + edges = set() + print('graph swarm {', file=out) +# print >>out, '"tl" [shape=point pos="0,0!"];' +# print >>out, '"tr" [shape=point pos="1638,0!"];' +# print >>out, '"ll" [shape=point pos="1638,1638!"];' +# print >>out, '"tr" [shape=point pos="0,1638!"];' + for dst, n in list(nodes.items()): + shape = 'point' + if 's' in n: + shape = n['s'] + + print('"%s" [shape=%s fillcolor="%s" label="" pos="%d,%d!"];' % + (dst, shape, n['c'], n['p'][0], n['p'][1]), file=out) + for e in n['conns']: + if (e, dst) in edges: + continue + + # only add an edge once to the .dot file + edges.add((e, dst)) + edges.add((dst, e)) + + style = 'solid' + col = 'gray' + if nodes[dst]['c'] != 'white' and nodes[e]['c'] != 'white': + style = 'solid' + col = 'black' + print('"%s" -- "%s" [style="%s" color="%s"];' % (e, dst, style, col), file=out) + + print('}', file=out) + out.close() + os.system('neato -n dht_frames/plot-%02d.dot -Tpng -o dht_frames/frame-%02d.png' % (frame, frame)) + + +frame = 0 +next_render_time = 100 +f = open('dht_lookups.txt') +for line in f: + if line.startswith('***'): + break + + kind = line[0:3].strip() + line = line[3:].strip().split(' ') + + if kind == '===': + continue + + t = int(line[0]) + if t > next_render_time: + plot_nodes(nodes, frame) + frame += 1 + next_render_time += 100 + # sys.exit(0) + + if kind == '<>': + p = calculate_pos(line[1], 0) + dst = '0.0.0.0' + if dst not in nodes: + nodes[dst] = {'conns': set(), 'p': p, 'c': 'blue', 's': 'circle'} + + p = calculate_pos(line[2], 25) + dst = '255.255.255.255' + if dst not in nodes: + nodes[dst] = {'conns': set(), 'p': p, 'c': 'yellow', 's': 'circle'} + elif kind == '->': + dst = line[3] + + if dst not in nodes: + src = get_origin(dst) + p = calculate_pos(line[2], int(line[1])) + nodes[dst] = {'conns': set(src), 'p': p, 'c': 'grey'} + nodes[dst]['c'] = 'grey' + + elif kind == '+': + dst = line[3] + src = line[4] + p = calculate_pos(line[2], int(line[1])) + if dst not in nodes: + nodes[dst] = {'conns': set(), 'p': p, 'c': 'white'} + nodes[dst]['conns'].add(src) + + elif kind == '<-': + dst = line[3] + nodes[dst]['c'] = 'green' + elif kind == 'x': + dst = line[3] + nodes[dst]['c'] = 'orange' + elif kind == 'X': + dst = line[3] + nodes[dst]['c'] = 'red' + +f.close() 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, 'w') + 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('